import Control.Monad (liftM2)
infixl 4 :+:, :-:
infixl 5 :*:, :/:
data Expr a = Const a
| (Expr a) :+: (Expr a)
| (Expr a) :-: (Expr a)
| (Expr a) :*: (Expr a)
| (Expr a) :/: (Expr a)
deriving (Show, Eq)
evalExpr (Const a) = a
evalExpr (a :+: b) = liftM2 (+) (evalExpr a) (evalExpr b)
evalExpr (a :-: b) = liftM2 (-) (evalExpr a) (evalExpr b)
evalExpr (a :*: b) = liftM2 (*) (evalExpr a) (evalExpr b)
evalExpr (a :/: b) = if (evalExpr b) == 0
then Nothing
else liftM2 (/) (evalExpr a) (evalExpr b)
这是我对数学和评价函数的象征性表示。我有一个问题,我对monad的知识有限,并且可能类型这个问题出现了,如果我的评估函数中有一个除零,我想要返回一个没有值。由于许多不同的原因,当我尝试运行evalExpr (Const 3)
或更复杂的事情时,它在运行时失败。有什么我想念的吗?
答案 0 :(得分:3)
我认为你想要的版本是:
import Control.Monad (liftM2)
infixl 4 :+:, :-:
infixl 5 :*:, :/:
data Expr a = Const a
| (Expr a) :+: (Expr a)
| (Expr a) :-: (Expr a)
| (Expr a) :*: (Expr a)
| (Expr a) :/: (Expr a)
deriving (Show, Eq)
evalExpr :: (Eq a, Num a, Fractional a) => (Expr a) -> Maybe a
evalExpr (Const a) = return a
evalExpr (a :+: b) = liftM2 (+) (evalExpr a) (evalExpr b)
evalExpr (a :-: b) = liftM2 (-) (evalExpr a) (evalExpr b)
evalExpr (a :*: b) = liftM2 (*) (evalExpr a) (evalExpr b)
evalExpr (a :/: b) = if (evalExpr b) == return 0
then Nothing
else liftM2 (/) (evalExpr a) (evalExpr b)
更改使用Maybe
案例中的正确monadic(在这种情况下为Const
)类型(return a
而不是a
)并且当您检查0时( if (evalExpr b) == return 0
而不是if (evalExpr b) == 0
)。
(并非两者都可以使用Just
而不是return
,因为它绝对是我们正在使用的Maybe monad。)
通过修复evalExpr
的类型,我能够更轻松地解决问题。 liftM2的使用使得Haskell表达式非常通用,因此许多不同版本的编译器都没有按预期工作或者无法使用令人困惑的消息进行编译。一旦类型被修复,编译器立即告诉我,Const
情况和导致问题的==
表达式。
您可能也有兴趣使用应用函数<$>
和<*>
代替liftM2
,因为无论有多少参数都可以使用这些函数:
import Control.Applicative ((<$>), (<*>))
...
evalExpr :: (Eq a, Num a, Fractional a) => (Expr a) -> Maybe a
evalExpr (Const a) = return a
evalExpr (a :+: b) = (+) <$> (evalExpr a) <*> (evalExpr b)
evalExpr (a :-: b) = (-) <$> (evalExpr a) <*> (evalExpr b)
evalExpr (a :*: b) = (*) <$> (evalExpr a) <*> (evalExpr b)
evalExpr (a :/: b) = if (evalExpr b) == return 0
then Nothing
else (/) <$> (evalExpr a) <*> (evalExpr b)
如果你想解除三个参数的函数,triad
说,那就是
triad <$> arg1 <*> arg2 <*> arg3
查找applicative functor以获取更多信息。有一个很好的描述here。
<强>更新强>
@gallais对分案的风格提出了一个观点。为了清晰起见,我没有提及它,但这可能就是我要做的:
evalExpr :: (Eq a, Num a, Fractional a) => (Expr a) -> Maybe a
evalExpr (Const a) = return a
evalExpr (a :+: b) = (+) <$> (evalExpr a) <*> (evalExpr b)
evalExpr (a :-: b) = (-) <$> (evalExpr a) <*> (evalExpr b)
evalExpr (a :*: b) = (*) <$> (evalExpr a) <*> (evalExpr b)
evalExpr (a :/: b) = (/) <$> (evalExpr a) <*> (failOn 0 $ evalExpr b)
failOn x a = case a of
Just x -> Nothing
_ -> a
或者更通用的版本利用MonadZero
类并采用函数:
...
evalExpr (a :/: b) = (/) <$> (evalExpr a) <*> (failOn (==0) $ evalExpr b)
failOn f a = do
b <- fmap f a
if b then mzero else a
还有其他monadic帮助函数在这种情况下很有用,例如guard
,when
和unless
,请参阅here。