您应该首先将[Maybe a] 转换为具有所有Just 元素的Maybe [a](如果其中任何一个是Nothing,则产生Nothing)。
这可以使用 sequence 来完成,使用 Maybe 的 Monad 实例:
GHCi> sequence [Just 1, Just 2]
Just [1,2]
GHCi> sequence [Just 1, Just 2, Nothing]
Nothing
definition of sequence 等价于:
sequence [] = return []
sequence (m:ms) = do
x <- m
xs <- sequence ms
return (x:xs)
所以我们可以将后一个例子扩展为:
do x <- Just 1
xs <- do
y <- Just 2
ys <- do
z <- Nothing
zs <- return []
return (z:zs)
return (y:ys)
return (x:xs)
使用do-notation expression of the monad laws,我们可以重写如下:
do x <- Just 1
y <- Just 2
z <- Nothing
return [x, y, z]
如果您知道 Maybe monad 是如何工作的,那么您现在应该了解 sequence 如何工作以实现所需的行为。 :)
然后您可以使用(<$>)(来自Control.Applicative;等效地,fmap 或liftM)将您的二进制函数折叠到列表中:
GHCi> foldl' (+) 0 <$> sequence [Just 1, Just 2]
Just 3
当然,你可以使用任何你想要的折叠,例如foldr、foldl1等。
另外,如果您希望列表为空时结果为Nothing,从而能够省略折叠的零值而不必担心空列表出现错误,那么您可以使用此折叠功能:
mfoldl1' :: (MonadPlus m) => (a -> a -> a) -> [a] -> m a
mfoldl1' _ [] = mzero
mfoldl1' f (x:xs) = return $ foldl' f x xs
同样适用于foldr、foldl 等。您需要为此导入Control.Monad。
但是,这必须稍有不同:
GHCi> mfoldl1' (+) =<< sequence [Just 1, Just 2]
Just 3
或
GHCi> sequence [Just 1, Just 2] >>= mfoldl1' (+)
Just 3
这是因为,与其他折叠不同,结果类型看起来像 m a 而不是 a;它是 bind 而不是 map。