这是我关于SO的第一个问题:)
我的Haskell知识非常有限,所以我需要一点帮助才能让我开始。 我有这个BNF语法:
num ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
int ::= num | num int
var ::= A | B | C | ... | Z
expr ::= var | int | - expr
| +(expr , expr) | *(expr , expr)
| let var be expr in expr
我已经编写了一个解析器,在SO的另一篇文章中提供了一些帮助。
我的数据类型是:
data Expr = Var Char | Tall Int | Sum Expr Expr | Mult Expr Expr | Neg Expr | Let Expr Expr Expr
我需要的是评估解析器输出的表达式(Expr,String)。我真的不知道从哪里开始这个任务。任何人都可以帮助我吗?
我不确定需要哪些更多信息,如有必要,我会发布。
答案 0 :(得分:7)
首先,在您的数据类型中,Let
构造函数的第一个数据应该只是变量标识符(在您的情况下为Char
)而不是Expr
。
尝试递归函数。您评估的是Expr
至Int
,因此基本上您希望签名的功能
evaluate :: Expr -> Int
然后开始匹配Expr
的构造函数并递归计算子表达式:
evaluate (Tall n) = n
evaluate (Sum e1 e2) = evaluate e1 + evaluate e2
当谈到Let
绑定和变量时,您需要扩展签名以另外传递将变量映射到其值的环境。这可以像(Char, Int)
对的列表一样简单。 Let
会将变量及其值添加到传递给in
表达式的环境中。所以你最终会得到类似的东西:
evaluate :: Expr -> Int
evaluate e = evaluate' e EmptyEnv
where evaluate' :: Expr -> Env -> Int
evaluate' (Tall n) _ = n
...
当然,如果使用未受let
约束的变量,则必须提供错误处理。
这有帮助吗?
答案 1 :(得分:4)
对于处理环境,您可以使用
如果您选择使用Data.Map模块,则值得编写
Data.Map.lookup
而不只是lookup
,
或 - 另一个解决方案 - 隐藏Prelude的lookup
和
import Prelude hiding (lookup)
以便不接收有关两个lookup
函数冲突的错误消息。
为简单起见,我首先使用简单的lookup
函数编写解决方案,即Prelude。
为了简单起见,我还没有加入错误。
环境:
module Env (Env) where
type Env = [Binding]
type Binding = (Char, Integer)
表达式:
module Expr where
data Expr = Var Char
| Tall Int
| Sum Expr Expr
| Mult Expr Expr
| Neg Expr
| Let Char Expr Expr
评价为:
module Semantics (evaluate) where
import Expr (Expr)
import Env (Env)
evaluate :: Expr -> Integer
evaluate = evaluate' []
evaluate' :: Env -> Expr -> Integer
evaluate' _ (Tall n) = n
evaluate' env (Var x) = case lookup x env of
Just n -> n
Nothing -> error ("Variable" ++ [x] ++ "is free!")
evaluate' env (Sum a b) = evaluate' env a + evaluate' env b
evaluate' env (Mult a b) = evaluate' env a * evaluate' env b
evaluate' env (Neg a) = - evaluate' env a
evaluate' env (Let x a b) = evaluate' ((x, a) : env) b
至于规划您的对象语言:在以后的版本中,值得计划一个策略,如何处理冲突的变量名称:
let A be 5 in (A +3)
很清楚,但应该是什么意思
let A be 5 in (let A be 3 in A)
在您的评估者的早期版本中,您不必进行此计划,因为lookup
函数将根据其定义中的内在默认行为“自动”决定情况。但是如果你不喜欢它的默认行为,你可能会用一个有意识的策略来扩充你的评估者来处理变量名冲突。
如果您在环境中计算表达式,并且表达式引用了环境中未包含的变量,那么解释器必须以某种方式报告错误。
你可以采取多种方式,最简单的方法:
使用error
函数,您可以使用用户定义的错误消息强制程序暂停。这种解决方案有缺点,但很容易编写。
您可以更改evaluate'
功能。它没有签名
evaluate' :: Env -> Expr -> Integer
但是,而不是
evaluate' :: Env -> Expr -> Maybe Integer
在这种情况下,你必须严格改变评估的定义。你不能再写了:
evaluate' env (Sum a b) = evaluate' env a + evaluate' env b
但需要更复杂的定义
evaluate' env (Sum a b) = case (evaluate' env a, evaluate' env b) of
(Just a0, Just b0) -> Just (a0 + b0)
_ -> Nothing
我们将Maybe-ed整数打包,求和,然后将它们打包回一个Maybe-ed整数。这就像在“包内”做总结。如果我们可以告诉Haskell它可以在“内部”中进行求和,那么我们可以节省很多工作。
我们可以这样做,如果我们利用那个可能是一个monad:我们可以使用为monad工作而设计的函数。这些辅助功能在Control.Monad库中提供。这里,liftM2是帮助我们围绕Maybe-packed值进行求和的函数:
evaluate' env (Sum a b) = liftM2 (+) (evaluate' env a) (evaluate' env b)
可以在Data.Maybe库中找到Maybe-ed值的其他一些辅助函数,但是在这里,我们不需要它们。
module Semantics (evaluate) where
import Expr (Expr)
import Env (Env)
import Control.Monad (liftM, liftM2)
evaluate :: Expr -> Maybe Integer
evaluate = evaluate' []
evaluate' :: Env -> Expr -> Maybe Integer
evaluate' _ (Tall n) = Just n
evaluate' env (Var x) = lookup x env
evaluate' env (Sum a b) = liftM2 (+) (evaluate' env a) (evaluate' env b)
evaluate' env (Mult a b) = liftM2 (*) (evaluate' env a) (evaluate' env b)
evaluate' env (Neg a) = liftM negate (evaluate' env a)
evaluate' env (Let x a b) = evaluate' ((x, a) : env) b