作为我在Haskell编写的迷你解释器的一部分,我正在编写一个执行以下操作的函数:如果是eval (App e1 e2)
,我想递归计算e1
({{ 1}}),将结果设置为eval e1
。然后使用Switch / Case,我想检查v1
的模式,如果它不是错误,则递归评估v1
(e2
)并将该值设置为eval e2
。使用这两个值v2
和v1
,然后我对这些值应用另一个函数(v2
)。
appVals
我想我可能已经弄明白了,但我并不完全确定我已正确完成开关/外壳部件。有什么想法/建议吗?
答案 0 :(得分:12)
你的案例陈述的第二部分不应该试图重新测试,因为你已经知道它不是Error
- 它与第一部分相匹配。 (跳过/= Error err
。)
eval e1 = v1
尝试重做您在开始时所做的评估。你不需要这样做。
以下是我认为你打算做的事情:
eval :: Exp -> Error Val
eval (App e1 e2) = case eval e1 of
Error _ -> Error "Not an application"
S v1 -> case eval e2 of -- nested case statement
Error _ -> Error "Not an application"
S v2 -> appVals v1 v2 -- match the S away
但这一切看起来都有些难看,所以让我们从Gabriel Gonzalez那里得到优秀的建议并从Error
开始应用。
instance Functor Error where
fmap f (Error e) = Error e -- pass through errors
fmap f (S x) = S (f x) -- edit successes
例如,fmap (+4) (Error "oops") = Error "oops"
而fmap (+4) (S 5) = S 9
。
如果这个fmap
对您来说都是新手,为什么不阅读Functors tutorial?
接下来让我们制作一个Applicative实例。 Applicative允许您使用复杂的功能,如简单的功能。
您需要在文件顶部import Control.Applicative
才能使其正常运行。
instance Applicative Error where
pure x = S x -- how to put ordinary data in
S f <*> S x = S (f x)
Error e <*> _ = Error e
_ <*> Error e = Error e
现在,如果没有任何错误,那么你就定义了
appVal' :: Val -> Val -> Val
eval' :: Exp -> Val
eval' (App e1 e2) = appVal' (eval' e1) (eval' e2)
使用applicative,我们可以使用<$>
,它有点像$
,除了它执行您在fmap
中定义的任何管道。
同样,<*>
有点像函数应用程序,除了额外的管道,所以我们可以定义
eval :: Exp -> Error Val
eval (App e1 e2) = appVals <$> eval e1 <*> eval e2
这是处理幕后错误的一种很好的干净方式,同时专注于功能。
答案 1 :(得分:4)
I left you yesterday(模数重命名)
eval (App e1 e2) = appVals <$> eval e1 <*> eval e2
这几乎就是你想要的,区别在于如果两个输入中的任何一个是错误,你想用特定的错误信息替换它。 (我不明白是为什么。)所以让我们写一个函数来做最后一点!
butError :: String -> Error a -> Error a
butError message (Error _) = Error message
butError _ noError = noError
现在您可以将eval
子句重写为
eval (App e1 e2) = appVals <$> butError message (eval e1) <*> butError message (eval e2)
where message = "Not an application"
答案 2 :(得分:3)
我假设你有一些看起来像这样的数据类型:
data Result a = Error String | Success a
问题的解决方案是:
(a)让您的Result
输入Monad
(b)使用Either String
代替结果,因为它已经有Monad
个实例。
无论你做什么,你都会写:
eval :: SyntaxTree a -> Result a
eval (App ef ex) = do
f <- eval ef
x <- eval ex
return (f x)
从技术上讲,Applicative
就足够了,你可以改写:
eval (App ef ex) = eval ef <*> eval ex
答案 3 :(得分:2)
在case
中,您需要为备选方案设置模式,而在那里不能有/= Error err
。
此外,=
表达式中的->
箭头右侧无法case
,因此-> eval e1 = v1
无效Haskell。
在你的情况下,你只关心它不是Error something
,但是一旦第一个模式不匹配,那就是自动情况,因为你不关心你得到了什么,你可以使用变量模式:
eval (App e1 e2) = appVals v1 v2 where
v1 = case (eval e1) of
Error err -> Error "Not an application"
other -> other
v2 = case (eval e2) of
Error err -> Error "Not an application"
other -> other