Je recommande d'utiliser une représentation sans nom localement pour les variables (telles que celles fournies par the extraordinarily useful bound
library) dans une syntaxe avec une liaison. Dans une représentation localement sans nom, les variables liées sont représentées à l'aide d'indices de Bruijn, et les variables non liées sont représentées à l'aide d'un nom. Cela vous épargne beaucoup d'effort quand il s'agit de choses comme l'alpha-équivalence et la capture-évitant la substitution, et de répondre à la question "cette variable est-elle liée?" devient très simple.
Généralement, une représentation localement sans nom est la sortie d'une opération de vérification de portée sur un arbre de syntaxe de surface. Nous pouvons régler le code de @HNTW's answer pour produire un terme contrôlé au lieu d'un Bool
:
data Scoped a = SVar a
| SLet (Scoped a) (Scope() Scoped a) -- The expression being let-bound, and the subexpression with the new variable in scope
| SLit Number
| SSub (Scoped a)
| SSum (Scoped a) (Scoped a)
| SMul (Scoped a) (Scoped a)
| SIf (Scoped a) (Scoped a) (Scoped a)
deriving (Functor, Foldable, Traversable)
instance Applicative Scoped where
pure = SVar
(<*>) = ap
instance Monad Scoped where
return = SVar
SVar a >>= g = g a
SLet expr scope >>= g = SLet (expr >>= g) (scope >>>= g)
SLit x >>= g = SLit x
SSub x >>= g = SSub (x >>= g)
SSum x y >>= g = SSum (x >>= g) (y >>= g)
SMul x y >>= g = SMul (x >>= g) (y >>= g)
SIf cond t f >>= g = SIf (cond >>= g) (t >>= g) (f >>= g)
scoped :: Expr -> Scoped Char
scoped (Var x) = SVar x
scoped (Let name expr sub) = SLet (scope expr) (abstract1 name $ scoped sub)
scoped (Lit x) = SLit x
scoped (Sub s) = SSub (scoped s)
scoped (Sum x y) = SSum (scoped x) (scoped y)
scoped (Mul x y) = SMul (scoped x) (scoped y)
scoped (If cond t f) = SIf (scoped cond) (scoped t) (scoped f)
bound
vues une expression comme « contenant » des noms a
. L'instance Monad
effectue une substitution ((>>=) :: Scoped a -> (a -> Scoped b) -> Scoped b
prend une fonction mappant les noms a
aux termes Scoped b
et les regroupe), et la bibliothèque bound
utilise cette opération de substitution pour implémenter des outils tels que abstract1
, qui lie une variable dans une sous-expression, produisant une nouvelle sous-expression sous une forme localement sans nom.
(Il ne serait pas déraisonnable de supprimer le type séparé Expr
et de produire un Scoped Char
directement comme sortie de votre analyseur.Les instances Foldable
et Traversable
permettent de trouver toutes les variables non liées dans une expression.
unboundVariables :: Scoped a -> [a]
unboundVariables = toList
de isClosed
bound
Combinator fait exactement ce que vous vouliez: il retourne False
si l'expression a des variables libres.
Si, pendant que vous marchez dans un champ, vous devez savoir si une variable particulière est liée ou libre, vous pouvez soit motif match sur B
et F
constructeurs de Var
, ou the supplied Prism
s:
isBound :: Var b f -> Bool
isBound = is _B
Pour plus d'informations sur le fonctionnement de bound
, voir this blog post ou these slides.
IMHO cette vérification devrait se produire à un niveau supérieur, après l'analyse. –
Ouais, je pense que oui, je le ferais au moins beaucoup plus facile. – iIllumination
Je pense que votre constructeur 'Let' est mal configuré. La chose sur le côté gauche de '=' dans 'let x = e1 dans e2' est un _name_ (ou, plus généralement, un _pattern), pas une expression arbitraire. 'let (si foo puis bar else baz) = x dans y' n'a pas de sens! –