更新:我添加了描述我的最终解决方案的an answer(提示:单Expr
数据类型不足)。
我writing是一个小表达语言的评估者,但我仍然坚持LetRec
构造。
这是语言:
type Var = String
type Binds = [(Var, Expr)]
data Expr
= Var Var
| Lam Var Expr
| App Expr Expr
| Con Int
| Sub Expr Expr
| If Expr Expr Expr
| Let Var Expr Expr
| LetRec Binds Expr
deriving (Show, Eq)
到目前为止这是评估员:
data Value
= ValInt Int
| ValFun Env Var Expr
deriving (Show, Eq)
type Env = [(Var, Value)]
eval :: Env -> Expr -> Either String Value
eval env (Var x) = maybe (throwError $ x ++ " not found")
return
(lookup x env)
eval env (Lam x e) = return $ ValFun env x e
eval env (App e1 e2) = do
v1 <- eval env e1
v2 <- eval env e2
case v1 of
ValFun env1 x e -> eval ((x, v2):env1) e
_ -> throwError "First arg to App not a function"
eval _ (Con x) = return $ ValInt x
eval env (Sub e1 e2) = do
v1 <- eval env e1
v2 <- eval env e2
case (v1, v2) of
(ValInt x, ValInt y) -> return $ ValInt (x - y)
_ -> throwError "Both args to Sub must be ints"
eval env (If p t f) = do
v1 <- eval env p
case v1 of
ValInt x -> if x /= 0
then eval env t
else eval env f
_ -> throwError "First arg of If must be an int"
eval env (Let x e1 e2) = do
v1 <- eval env e1
eval ((x, v1):env) e2
eval env (LetRec bs e) = do
env' <- evalBinds
eval env' e
where
evalBinds = mfix $ \env' -> do
env'' <- mapM (\(x, e') -> eval env' e' >>= \v -> return (x, v)) bs
return $ nub (env'' ++ env)
这是我要评估的测试功能:
test3 :: Expr
test3 = LetRec [ ("even", Lam "x" (If (Var "x")
(Var "odd" `App` (Var "x" `Sub` Con 1))
(Con 1)
))
, ("odd", Lam "x" (If (Var "x")
(Var "even" `App` (Var "x" `Sub` Con 1))
(Con 0)
))
]
(Var "even" `App` Con 5)
修改
根据Travis的回答和Luke的评论,我更新了我的code以使用MonadFix实例作为Error monad。前面的例子现在工作正常!但是,下面的示例无法正常工作:
test4 :: Expr
test4 = LetRec [ ("x", Con 3)
, ("y", Var "x")
]
(Con 0)
评估时,评估程序循环,没有任何反应。我猜我在这里做了一些太严格的事,但我不确定它是什么。我是否违反了MonadFix的一项法律?
答案 0 :(得分:4)
当Haskell投出合适时,这通常表明你没有清楚地想到你的问题的核心问题。在这种情况下,问题是:您希望将哪种评估模型用于您的语言?按值调用或按需调用?
您将环境表示为[(Var,Value)]
表示您希望使用按值调用,因为在将Expr
存储到环境中之前,每个Value
都会立即评估为letrec
。但是Value
并不顺利,你的第二个例子显示了!
此外,请注意主机语言(Haskell)的评估模型将干扰您要实现的语言的评估模型;事实上,这就是你目前正在使用的例子:尽管他们的目的是你的目的,你的letrec
并没有被评估为弱头正常形式。
除非您对小表达语言的评估模型有清晰的了解,否则您将无法在letrec
或错误检查工具上取得很大进展。
修改强> 有关按值调用语言的{{1}}示例规范,请查看Ocaml Manual。在最简单的层面上,它们只允许使用lambda表达式的右侧,即语法上已知为值的东西。
答案 1 :(得分:2)
也许我错过了什么,但是没有以下工作?
eval env (LetRec bs ex) = eval env' ex
where
env' = env ++ map (\(v, e) -> (v, eval env' e)) bs
对于您的更新版本:以下方法如何?它可以在您的测试用例中按预期工作,并且不会丢失LetRec
表达式中的错误:
data Value
= ValInt Int
| ValFun EnvWithError Var Expr
deriving (Show, Eq)
type Env = [(Var, Value)]
type EnvWithError = [(Var, Either String Value)]
eval :: Env -> Expr -> Either String Value
eval = eval' . map (second Right)
where
eval' :: EnvWithError -> Expr -> Either String Value
eval' env (Var x) = maybe (throwError $ x ++ " not found")
(join . return)
(lookup x env)
eval' env (Lam x e) = return $ ValFun env x e
eval' env (App e1 e2) = do
v1 <- eval' env e1
v2 <- eval' env e2
case v1 of
ValFun env1 x e -> eval' ((x, Right v2):env1) e
_ -> throwError "First arg to App not a function"
eval' _ (Con x) = return $ ValInt x
eval' env (Sub e1 e2) = do
v1 <- eval' env e1
v2 <- eval' env e2
case (v1, v2) of
(ValInt x, ValInt y) -> return $ ValInt (x - y)
_ -> throwError "Both args to Sub must be ints"
eval' env (If p t f) = do
v1 <- eval' env p
case v1 of
ValInt x -> if x /= 0
then eval' env t
else eval' env f
_ -> throwError "First arg of If must be an int"
eval' env (Let x e1 e2) = do
v1 <- eval' env e1
eval' ((x, Right v1):env) e2
eval' env (LetRec bs ex) = eval' env' ex
where
env' = env ++ map (\(v, e) -> (v, eval' env' e)) bs
答案 2 :(得分:1)
回答我自己的问题;我想分享我想出的最终解决方案。
正如海因里希正确pointed out,我并没有真正考虑评估令的影响。
在严格(按值调用)语言中,已经是值(弱头正常形式)的表达式与仍需要一些评估的表达式不同。一旦我在我的数据类型中对这种区别进行了编码,就会出现一切:
type Var = String
type Binds = [(Var, Val)]
data Val
= Con Int
| Lam Var Expr
deriving (Show, Eq)
data Expr
= Val Val
| Var Var
| App Expr Expr
| Sub Expr Expr
| If Expr Expr Expr
| Let Var Expr Expr
| LetRec Binds Expr
deriving (Show, Eq)
与我的原始Expr
数据类型的唯一区别在于,我将两个构造函数(Con
和Lam
)拉出到自己的数据类型Val
中。 Expr
数据类型有一个新的构造函数Val
,这表示值也是一个有效的表达式。
使用自己的数据类型中的值,可以将它们与其他表达式分开处理,例如letrec
绑定只能包含值,而不能包含其他表达式。
这种区别也在其他严格的语言中进行,例如C,其中只能在全局范围内定义函数和常量。
有关更新的评估函数,请参阅complete code。