表达式单子

Expression Monad

我接到任务:

data Exp = Con Int | Sum Exp Exp | Division Exp Exp

编写函数evalM,计算表达式Exp并将其放入Maybe。 不允许使用构造函数 Just,必须用 DO 或 >>=.

来解决

所以我的代码是:

evalM1 :: Exp -> Int
evalM1 e = evalM2 e where
           evalM2 :: Exp -> Int
           evalM2 (Con i) = (i)
           evalM2 (Sum e1 e2) = (evalM2 e1)+(evalM2 e2)
           evalM2 (Division e1 e2) = div (evalM2 e1) (evalM2 e2)

evalM :: Exp -> Maybe Int
evalM e = do
          return (evalM1 e)

-- Second try
evalM e = do
          y <- Just (evalM1 e)
          return y

所以我的问题是为什么第一个 evalM 在没有 Just 的情况下工作而第二个 evalM 仅在我使用 Just (y <- Just..) 时工作​​?

也许有人可以用 >>= 把解决方案写给我。

非常感谢!

解决方案:

evalM :: Exp -> Maybe Int
evalM (Con i) = return i 
evalM (Neg x) = evalM x >>= \v -> return (-v)
evalM (Sum e1 e2) = evalM e1 >>= \a -> evalM e2 >>= \b -> (return (a+b))
evalM (Division e1 e2) = evalM e1 >>= \a -> evalM e2 >>= (\b -> guard(b/=0) >> (return (div a b)))

> *Test> evalM (Division (Con 1) (Division (Con 0) (Con 1))) ~> Nothing
> *Test> evalM (Division (Con 1) (Sum (Con 2) (Neg (Con 2)))) ~> Nothing

这里有一个提示。

首先,您必须了解为什么评估结果是 Maybe Int 而不是更简单的 Int。直观地说,Maybe Int 包含 Int 的所有可能值,外加一个特殊值 (Nothing)。附加价值是什么?

很可能,该附加值将用于捕获被零除的错误。也就是说,如果我们需要在表达式中除以零,我们应该 return Nothing 而不是导致运行时错误。

您的 evalM1 函数忽略了这个事实,只是尝试除以数字,returning 一个简单的 Int。不过,这个 Int return 类型是半谎言,因为除以零并没有真正处理。除以零会导致运行时错误,而不是更好的特殊值 Nothing.

请注意,我们不能简单地定义 evalM :: Exp -> Maybe Int 换行 evalM1。代码如

evalM e = return (evalM1 e)

会打字,但不会作为特殊情况处理被零除。

我建议您忽略 evalM1,而是从头开始构建 evalM。您可以遵循类似的递归模式:

evalM :: Exp -> Maybe Int
evalM (Con i) = ...
evalM (Sum e1 e2) = ...
evalM (Division e1 e2) = ...

对于求和的情况:请注意我们不能写递归调用 evalM e1 + evalM e2 因为我们不能 + 两个 Maybe Int 因为它们不是数字。然而,这不是一个真正的问题。我们可以改为在 do 块内执行这两个调用,获取它们的结果(如果有的话,回想一下它们可以 return Nothing),然后最后是 +return总和。

除法类似,但记得检查0

最后,让我谈谈你的 "question as asked"。为什么 x <- Just number 只适用于 Just。好吧,那是因为我们在 Maybe monad 内部工作,所以 <- 之后的每个表达式都必须具有 Maybe something 形式的类型。所以我们不能写x <- (42 :: Int)。如果没有 Just,我们可以在 do 块中写入 let x = (42 :: Int),但您不需要那个。