我正在使用Haskell和monad,但我对它们有点困惑 这是我的代码,但我收到错误,我不知道如何改进我的代码。
doAdd :: Int -> Int -> Maybe Int
doAdd x y = do
result <- x + y
return result
答案 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
(以及许多其他函数)类似的效用函数)。
另一方面,safeDiv
和allSqrt
是您无法在不使用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。)