Haskell中的符号数学

时间:2014-11-11 23:22:06

标签: haskell

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)或更复杂的事情时,它在运行时失败。有什么我想念的吗?

1 个答案:

答案 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帮助函数在这种情况下很有用,例如guardwhenunless,请参阅here