【发布时间】:2021-02-14 08:14:14
【问题描述】:
假设我正在创建一个可以抛出错误的简单解释器,例如
type Error = String
data Term = Con Int | Div Term Term
eval :: (MonadError Error m) => Term -> m Int
eval (Con a) = return a
eval (Div u v) = do
a <- eval u
b <- eval v
if b == 0 then
throwError "Division by zero"
else
return $ a `div` b
具体错误处理程序的典型选择是Either Error。
runEval :: Term -> Either Error Int
runEval = eval
现在假设我想扩展这个解释器来处理非确定性。例如,我可以添加一个术语 Choice Term Term,它可以评估其第一个或第二个参数。
data Term = Con Int | Div Term Term | Choice Term Term
然后我可以将具体评估表示为[Either Error Int],其中列表中的每个项目都代表一个可能的评估。但是,我正在努力如何在不修改 Con 和 Div 案例的情况下将 Choice 案例添加到我的 eval 函数中。
我尝试了什么:
eval :: (MonadError Error m, MonadPlus m) => Term -> m Int
-- The Con and Div cases remain unchanged.
eval (Choice u v) = do
w <- return u `mplus` return v -- pick either u or v
eval w
runEval :: Term -> [Either Error Int]
runEval = runExceptT . eval
但是,这只会返回第一个可能的结果,并且不会回溯。
> let t = Con 1 `Div` (Choice (Con 0) (Con 1))
> runEval t
[Left "Division by zero"]
我的预期:[Left "Division by zero", Right 1]。
结合非确定性和错误处理的正确方法是什么?
【问题讨论】:
-
你可以看看monad transformers。这或多或少用于创建 monad 上下文的“堆栈”。
-
@WillemVanOnsem 谢谢你的评论。实际上,目标是使用转换器将错误单子和列表单子结合起来。这实际上就是我在
runEval中所做的:在那里我使用runExceptT,我认为这意味着Haskell 将有效地使用monadExceptT Error [] Int进行我的计算。但是,正如我的问题所解释的那样,它并没有像我预期的那样工作。 -
目前,你的
eval是ExceptT Error [] Int,但我想你想要的是ListT (ExceptT Error) Int。 -
@ArtemPelenitsyn 对于
ListT (ExceptT Error),一个错误就会停止计算。runEval会有一个类似Term -> Either Error [Int]的签名。
标签: haskell monads monad-transformers non-deterministic