Haskell 中的符号数学

Kyl*_*ean 5 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)
Run Code Online (Sandbox Code Playgroud)

这是我对数学和评估函数的符号表示。我对单子的了解有限,并且可能出现此问题的类型是因为我想在我的评估函数中除以零时返回无值。由于许多不同的原因,当我尝试运行evalExpr (Const 3)或任何更复杂的原因时,它在运行时失败。我有什么遗漏的吗?

Vic*_*ith 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)
Run Code Online (Sandbox Code Playgroud)

更改是在 case (而不是)以及检查 0(而不是)时使用正确的单子(在本例Maybe中)类型。Constreturn aaif (evalExpr b) == return 0if (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)
Run Code Online (Sandbox Code Playgroud)

如果你想提升一个包含三个参数的函数,triad那么它就是

triad <$> arg1 <*> arg2 <*> arg3
Run Code Online (Sandbox Code Playgroud)

查找应用函子以获取更多信息。这里有一个很好的描述。

更新

@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
Run Code Online (Sandbox Code Playgroud)

或者一个更通用的版本,利用该类MonadZero并采用一个函数:

...
evalExpr (a :/: b) = (/) <$> (evalExpr a) <*> (failOn (==0) $ evalExpr b)

failOn f a = do
    b <- fmap f a
    if b then mzero else a
Run Code Online (Sandbox Code Playgroud)

还有其他一元辅助函数在这种情况下很有用,例如guardwhenunless,请参阅此处