也许使用唯一数据类型的Int表达式

时间:2019-04-22 11:59:33

标签: haskell

我编写了一种独特的数据类型来表示基本数学(加法,乘法等),并且它可以工作-但是,当我尝试将其转换为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)

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

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)
   |                                        ^^^^^^^^^

我想念什么?这让我发疯。

2 个答案:

答案 0 :(得分:7)

第一个问题是优先级。不用写:

Just (expression e1) * (expression e2)

您可能想要:

Just (expression e1 * expression e2)

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

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

对于一个a类型的Num,它需要两个a,并返回一个a。专用于Int,那就是:

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

但是expression返回一个Maybe Int!因此,我们需要某种与Maybe相乘的方法。让我们自己编写函数:

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

答案 1 :(得分:4)

这里有两个问题:

 Just (expression e1)  + (expression e2)

被解释为:

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

因此,这意味着您已将左值包装在Just中,而另一个未包装,这没有多大意义。

第二,expression e1expression e2的类型均为Maybe Int,因此这意味着您无法将这两个变量加在一起。我们可以执行模式匹配。

幸运的是,有一个更优雅的解决方案:对于大多数 模式,我们可以使用liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c。对于MaybeliftM2将使用一个函数f :: a -> b -> c和两个Maybe,如果两者都是Just,它将在以下值上调用该函数:包裹在Just中,然后将结果也包裹在Just中。

对于除法情况,我们首先必须使用expression函数获得分母的结果,如果它是{em> not 等于的Just设为零,那么我们可以使用fmap :: Functor f => (a -> b) -> f a -> f b函数将值映射到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 b(<*>) :: 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