1

我编写了一个独特的数据类型来表达基本数学(加法、乘法等)并且它可以工作 - 但是,当我尝试将其转换为 Maybe 语句时,所有数学都不起作用。我相信这是一个语法错误,但我尝试了额外的括号等等,但我无法弄清楚。通常,Maybe 语句很容易,但我不明白为什么它总是抛出问题。

这是我创建的数据类型(带有示例):

data Math = Val Int
           | Add Math Math
           | Sub Math Math
           | Mult Math Math
           | Div Math Math
    deriving Show

ex1 :: Math
ex1 = Add1 (Val1 2) (Val1 3)

ex2 :: Math
ex2 = Mult (Val 2) (Val 3)

ex3 :: Math
ex3 = Div (Val 3) (Val 0)

这是代码。唯一的 Nothing 返回应该是除以零。

expression :: Math -> Maybe Int
expression (Val n)        = Just n
expression (Add e1 e2)    = Just (expression e1) + (expression e2)
expression (Sub e1 e2)    = Just (expression e1) - (expression e2)
expression (Mult e1 e2)   = Just (expression e1) * (expression e2)
expression (Div e1 e2)
  | e2 /= 0               = Just (expression e1) `div` (expression e2)
  | otherwise             = Nothing

对于每个单独的数学方程,我都会得到相同的错误,即使我删除了其他的,所以我确定它是语法。该错误使它看起来像 Maybe 中的 Maybe 但是当我这样做e1 /= 0 && e2 /= 0 = Just (Just (expression e1)div时(expression e2)),我得到了同样的错误:

 * Couldn't match type `Int' with `Maybe Int'
    Expected type: Maybe (Maybe Int)
      Actual type: Maybe Int
 * In the second argument of `div', namely `(expression e2)'
    In the expression: Just (expression e1) `div` (expression e2)
    In an equation for `expression':
      expression (Div e1 e2)
        | e1 /= 0 && e2 /= 0 = Just (expression e1) `div` (expression e2)
        | otherwise = Nothing
   |
56 |   | e1 /= 0 && e2 /= 0 = Just (expression e1) `div` (expression e2)
   |                                        ^^^^^^^^^

我错过了什么?这让我疯狂。

4

2 回答 2

7

所以第一个问题是优先级。而不是写:

Just (expression e1) * (expression e2)

你可能想要:

Just (expression e1 * expression e2)

第二个问题是类型。看一下 (*) 的类型,例如:

>>> :t (*)
(*) :: Num a => a -> a -> a

它说,对于某些类型aa Num,它需要两个as 并返回一个a。专门用于Int,那将是:

(*) :: Int -> Int -> Int

expression返回一个Maybe Int!所以我们需要一些方法来与Maybes 相乘。让我们自己编写函数:

multMaybes :: Maybe Int -> Maybe Int -> Maybe Int
multMaybes Nothing _ = Nothing
multMaybes _ Nothing = Nothing
multMaybes (Just x) (Just y) = Just (x * y)

因此,如果乘法的任何一方都失败了(即你发现了一个被零除),那么整个事情都会失败。现在,我们需要为每个运算符执行一次:

addMaybes Nothing _ = Nothing
addMaybes _ Nothing = Nothing
addMaybes (Just x) (Just y) = Just (x + y)

subMaybes Nothing _ = Nothing
subMaybes _ Nothing = Nothing
subMaybes (Just x) (Just y) = Just (x - y)

等等。但是我们可以看到这里有很多重复。幸运的是,已经有一个函数可以实现这种模式:liftA2.

multMaybes = liftA2 (*)
addMaybes  = liftA2 (+)
subMaybes  = liftA2 (-)

最后,还有两个小问题。首先,你说:

expression (Div e1 e2)
  | e2 /= 0               = Just (expression e1) `div` (expression e2)

e2不是一个Int!这是表达式类型。您可能想检查递归调用的结果是否为 0。

第二个问题是你不必要地把东西包裹在里面Just:我们可以删除一层。

毕竟,我们可以这样编写你的函数:

expression :: Math -> Maybe Int
expression (Val n)        = Just n
expression (Add e1 e2)    = liftA2 (+) (expression e1) (expression e2)
expression (Sub e1 e2)    = liftA2 (-) (expression e1) (expression e2)
expression (Mult e1 e2)   = liftA2 (*) (expression e1) (expression e2)
expression (Div e1 e2)
  | r2 /= Just 0          = liftA2 div (expression e1) r2
  | otherwise             = Nothing
  where r2 = expression e2
于 2019-04-22T12:18:59.243 回答
4

这里有两个问题:

 Just (expression e1)  + (expression e2)

被解释为:

(Just (expression e1)) + (expression e2)

所以这意味着你已经将左值包装在 a 中Just,而另一个没有,这没有多大意义。

其次, bothexpression e1expression e2have type Maybe Int,因此这意味着您不能将这两个加在一起。我们可以进行模式匹配。

liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c幸运的是,有一个更优雅的解决方案:我们可以利用大多数模式。因为MaybeliftM2接受一个函数f :: a -> b -> c和两个Maybes,如果两者都是Justs,它将在Justs 中包装的值上调用该函数,然后将结果也包装在 aJust中。

至于除法的情况,我们首先必须用expression函数获得分母的结果,如果那Just等于零的a,那么我们可以用fmap :: Functor f => (a -> b) -> f a -> f b函数映射给定的a Just(分子的值)中的值当然,分子是Just

import Control.Monad(liftM2)

expression :: Math -> Maybe Int
expression (Val n)  = Just n
expression (Add e1 e2) = liftM2 (+) (expression e1) (expression e2)
expression (Sub e1 e2) = liftM2 (-) (expression e1) (expression e2)
expression (Mult e1 e2) = liftM2 (*) (expression e1) (expression e2)
expression (Div e1 e2) | Just v2 <- expression e2, v2 /= 0 = fmap (`div` v2) (expression e1)
                       | otherwise = Nothing

或者我们可以像@RobinZigmond 说的那样使用(<$>) :: Functor f => (a -> b) -> f a -> f band (<*>) :: Applicative f => f (a -> b) -> f a -> f b

expression :: Math -> Maybe Int
expression (Val n)  = Just n
expression (Add e1 e2) = (+) <$> expression e1 <*> expression e2
expression (Sub e1 e2) = (-) <$> expression e1 <*> expression e2
expression (Mult e1 e2) = (*) <$> expression e1 <*> expression e2
expression (Div e1 e2) | Just v2 &lt;- expression e2, v2 /= 0 = (`div` v2) <$> expression e1
                       | otherwise = Nothing
于 2019-04-22T12:13:13.297 回答