简体   繁体   English

Haskell-解析表达式中变量的状态(变量是否绑定?)

[英]Haskell - State of variables in parsed expression (Is the variable bound or not?)

Hi so I got a function that takes a string and calculates the value, either an int or an bool. 嗨,我得到了一个接受字符串并计算值的函数,它可以是int或bool。 The string can be ie: " * let X = 3 in + X 1 2 ", which represent " (let X = 3 in (X + 1)) * 2 ". 字符串可以是:“ * let X = 3 in + X 1 2 ”,它表示“ (let X = 3 in (X + 1)) * 2 ”。 Which will give me 8 or false if I ask for a bool. 如果我要布尔值,这将给我8或假。 Simple enough. 很简单。 But I also want to check the state of bound variables in the parser. 但我也想检查解析器中绑定变量的状态。 Ie if I use a string like this " * let X = 3 in + X 1 X " as input, it will represent " (let X = 3 in (X + 1)) * X " which makes the last X unbound. 即,如果我使用像这样的字符串“ * let X = 3 in + X 1 X ”作为输入,它将表示“ (let X = 3 in (X + 1)) * X ”,这使最后一个X不受约束。 This will then give me an error saying that the variable A is unbound or something. 然后,这将给我一个错误,指出变量A是未绑定的。 If someone could give me any ideas on how I could do this, I would be very much appreciated! 如果有人可以给我关于如何做到的任何想法,我将不胜感激!

Expample of parsed string: 解析字符串的示例:

((let X = 3 in (X + 1)) * 2)

Will give me: 会给我的:

(Mul (Let (Vari X) (Lit (Single 3)) (Sum (Vari X) (Lit (Single 1)))) (Vari X))

So my question is basiclly how I can check if the parsed string has any unbound variables. 因此,我的问题基本上是我如何检查已解析的字符串是否具有任何未绑定变量。

data Number = Single Int | Many Int Number deriving (Eq, Show)

data Expr = Vari Chara | Lit Number | Sub Expr | Sum Expr Expr | Mul Expr Expr | Let Expr Expr Expr | If Expr Expr Expr deriving (Eq, Show)

Recurse down the parsed expression, holding a set of bound variables. 递归向下解析的表达式,其中包含一组绑定变量。 Every time you come across a Let , record it, every time you come across a Var , check that it's in your set, and every time you come across something else, check its subexpressions. 每次遇到Let ,记录一次,每次遇到Var ,检查它是否都在集合中,以及每次遇到其他内容时,请检查其子表达式。

checkVars' :: Set String -> Expr -> Bool
checkVars' bound (Var v) = S.member v set
checkVars' bound (Let var val expr) = checkVars' bound val && checkVars' (insert var bound) expr
checkVars' bound (Mul expr1 expr2) = checkVars' bound expr1 && checkVars' bound expr2
-- Similar for other ctors...

checkVars :: Expr -> Bool
checkVars = checkVars' S.empty

You can also work in the opposite direction, building up a set of free variables from the bottom up, and removing variables whenever you see them bound by Let . 您也可以朝相反的方向工作,从下至上构建一组自由变量,并在看到它们由Let绑定时将其删除。 (This feels more natural.) (感觉更自然。)

freeVars :: Expr -> Set String
freeVars (Var v) = S.singleton v
freeVars (Let var val expr) = S.union (freeVars val) $ S.delete var $ freeVars expr
freeVars (Add expr1 expr2) = S.union expr1 expr2
-- etc.

checkVars :: Expr -> Bool
checkVars = S.null . freeVars

-- With recursion-schemes
data ExprF a = Add a a
             | Mul a a
             | Let String a a
             | Var String
             | Lit Int
             deriving (Eq, Show, Read, Functor, Foldable, Traversable)

type Expr = Fix ExprF

freeVars :: Expr -> Set String
freeVars = cata go -- -XLambdaCase, anyone?
  where go :: ExprF (Set String) -> Set String
        go (Var var) = S.singleton var
        go (Let var val expr) = S.union val $ S.delete var expr
        go e = foldl S.union S.empty e -- Handle everything else

I recommend using a locally nameless representation for variables (such as that provided by the extraordinarily useful bound library ) in a syntax with binding. 我建议使用带绑定的语法对变量(例如由非常有用的bound提供的变量)使用本地无名表示。 In a locally nameless representation, bound variables are represented using de Bruijn indices, and unbound variables are represented using a name. 在局部无名称表示中,绑定变量使用de Bruijn索引表示,未绑定变量使用名称表示。 This saves you a whole lot of effort when it comes to things like alpha-equivalence and capture-avoiding substitution, and answering the question "is this variable bound?" 这在涉及alpha等效性和避免捕获的替换之类的问题上为您节省了很多精力,并回答了“此变量是否绑定?”的问题。 becomes very straightforward. 变得非常简单。

Typically a locally nameless representation is the output of a scope-checking operation on a surface syntax tree. 通常,本地无名表示形式是表面语法树上范围检查操作的输出。 We can adjust the code from @HNTW's answer to produce a checked term instead of a Bool : 我们可以根据@HNTW的答案调整代码,以产生一个选中的词条,而不是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 views an expression as a "container" of names a . bound将表达式视为名称a的“容器”。 The Monad instance performs substitution ( (>>=) :: Scoped a -> (a -> Scoped b) -> Scoped b takes a function mapping names a to terms Scoped b and grafts them together), and the bound library makes use of this substitution operation to implement tools like abstract1 , which binds a variable in a subexpression, producing a new subexpression in a locally nameless form. Monad实例执行替换( (>>=) :: Scoped a -> (a -> Scoped b) -> Scoped b接受一个将名称a映射到术语Scoped b并将其Scoped b在一起的函数),并且bound库使用替换操作的实现,以实现诸如abstract1工具,该工具在子表达式中绑定变量,从而以局部无名的形式生成新的子表达式。

(It wouldn't be unreasonable to do away with the separate Expr type, and just produce a Scoped Char directly as the output of your parser.) (废除单独的Expr类型并不是没有道理的,只需直接生成Scoped Char作为解析器的输出即可。)

Scoped 's Foldable and Traversable instances let you find all the unbound variables in an expression. ScopedFoldableTraversable实例使您可以查找表达式中的所有未绑定变量。

unboundVariables :: Scoped a -> [a]
unboundVariables = toList

bound 's isClosed combinator does just what you wanted: it returns False if the expression has any free variables. boundisClosed组合器会执行​​您想要的操作:如果表达式具有任何自由变量,则返回False

If, as you walk under a scope, you need to know if a particular variable is bound or free, you can either pattern-match on Var 's B and F constructors, or use the supplied Prism s : 如果在遍历作用域时需要了解某个特定变量是否绑定或自由,则可以在VarBF构造函数上进行模式匹配,或使用提供的Prism

isBound :: Var b f -> Bool
isBound = is _B

For more on how bound works, see this blog post or these slides . 有关bound如何工作的更多信息,请参阅此博客文章这些幻灯片

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM