改进我的Haskell monads代码

时间:2013-12-04 00:37:19

标签: haskell monads

我正在使用Haskell和monad,但我对它们有点困惑 这是我的代码,但我收到错误,我不知道如何改进我的代码。

doAdd :: Int -> Int -> Maybe Int    
doAdd x y = do 
result <- x + y
return result

4 个答案:

答案 0 :(得分:8)

让我们批判性地看一下您正在编写的函数的类型:

doAdd :: Int -> Int -> Maybe Int

Maybe monad的要点是使用包装 Maybe类型构造函数的类型。在您的情况下,两个Int参数只是普通Int s,而+函数总是生成Int,因此不需要monad。

如果相反,您的函数将Maybe Int作为参数,那么您可以使用do表示法来处理幕后的Nothing案例:

doAdd :: Maybe Int -> Maybe Int -> Maybe Int
doAdd mx my = do x <- mx
                 y <- my
                 return (x + y)

example1 = doAdd (Just 1) (Just 3)   -- => Just 4
example2 = doAdd (Just 1) Nothing    -- => Nothing
example3 = doAdd Nothing (Just 3)    -- => Nothing
example4 = doAdd Nothing Nothing     -- => Nothing

但是我们可以从中提取一个模式:你正在做的事情,更一般地说,正在采取一个函数((+) :: Int -> Int -> Int)并使它适用于它想要的参数在“内部”monad的情况下。我们可以抽象出具体的函数(+)和特定的monad(Maybe)并得到这个通用函数:

liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
liftM2 f ma mb = do a <- ma
                    b <- mb
                    return (f a b)

现在使用liftM2,您可以写:

doAdd :: Maybe Int -> Maybe Int -> Maybe Int
doAdd = liftM2 (+)

我选择名称liftM2的原因是因为这实际上是一个库函数 - 您不需要编写它,您可以导入Control.Monad模块并且您将获得它免费。

使用Maybe monad会有什么更好的例子?当您执行与+不同的操作时,本质上可以产生Maybe结果。一个想法是,如果你想通过0错误抓住除法。您可以编写div函数的“安全”版本:

-- | Returns `Nothing` if second argument is zero.
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv x y = Just (x `div` y)

现在在这种情况下,monad确实变得更有用:

-- | This function tests whether `x` is divisible by `y`.  Returns `Nothing` if
-- division by zero.
divisibleBy :: Int -> Int -> Maybe Bool
divisibleBy x y = do z <- safeDiv x y
                     let x' = z * y
                     return (x == x')

另一个更有趣的monad示例是,如果您的操作返回多个值 - 例如,正方形和负方形根:

 -- Compute both square roots of x.
allSqrt x = [sqrt x, -(sqrt x)]

 -- Example: add the square roots of 5 to those of 7.  
example = do x <- allSqrt 5
             y <- allSqrt 7
             return (x + y)

或使用上面的liftM2

example = liftM2 (+) (allSqrt 5) (allSqrt 7)

所以无论如何,一个好的经验法则是:永远不会“污染”具有monad类型的函数,如果它真的不需要它。您的原始doAdd - 甚至我的重写版本 - 都违反了此经验法则,因为该功能的作用是添加,但添加与Maybe无关 - Nothing处理只是我们在核心函数(+)之上添加的行为。这个经验法则的原因是任何使用monad的函数都可以通常适用于添加任何monad的行为,使用实用函数,如liftM2(以及许多其他函数)类似的效用函数)。

另一方面,safeDivallSqrt是您无法在不使用Maybe[]的情况下真正编写所需功能的示例。 if 你正在处理类似的函数,那么monad通常是一个方便的抽象来消除样板代码。

答案 1 :(得分:3)

更好的例子可能是

justPositive :: Num a => a -> Maybe a
justPositive x
    | x <= 0 = Nothing
    | otherwise = Just x

addPositives x y = do
    x' <- justPositive x
    y' <- justPositive y
    return $ x' + y'

这将使用do notation过滤掉传递给函数的任何非正值

答案 2 :(得分:1)

这不是你编写代码的方式。 <-运算符用于获取monad的值 out x + y的结果只是一个数字,而不是包含数字的monad。

在这里,记谱法实际上是完全浪费的。如果你受到束缚并决心以这种方式写作,那就必须看起来像这样:

doAdd x y = do
  let result = x + y
  return result

但这只是一个漫长的版本:

doAdd x y = return $ x + y

这相当于

doAdd x y = Just $ x + y

你实际上是这样编写的。

答案 3 :(得分:0)

您提供的用例并不能证明符号,但这是一个更常见的用例 - 您可以将这种类型的函数链接在一起。

func::Int->Int->Maybe Int -- func would be a function like divide, which is undefined for division by zero

main = do
    result1 <- func 1 2
    result2 <- func 3 4
    result3 <- func result1 result2
    return result3

无论如何,这是monad的重点,将a-&gt; m a类型的函数链接在一起。

当以这种方式使用时,Maybe monad的行为与Java中的异常非常相似(如果要传播消息,可以使用Either。)