折叠在哈斯克尔的Maybes

时间:2011-09-18 20:22:07

标签: haskell monads

为了学习Haskell,我遇到过一种情况,我希望在列表中进行折叠,但我的累加器是一个Maybe。然而,我正在折叠的函数接受了Maybe中的“提取”值,如果一个失败,它们都会失败。我有一个解决方案,我发现kludgy,但我知道像我一样小Haskell,我相信应该有一个更好的方法。假设我们有以下玩具问题:我们想要列出一个列表,但由于某种原因四肢是坏的,所以如果我们在任何时候尝试总计四个,我们想要返回Nothing。我目前的解决方案如下:

import Maybe

explodingFourSum :: [Int] -> Maybe Int
explodingFourSum numberList =
    foldl explodingFourMonAdd (Just 0) numberList
    where explodingFourMonAdd =
        (\x y -> if isNothing x
                    then Nothing
                    else explodingFourAdd (fromJust x) y)

explodingFourAdd :: Int -> Int -> Maybe Int
explodingFourAdd _ 4 = Nothing
explodingFourAdd x y = Just(x + y)

基本上,有没有办法使用某种Monad折叠清除或消除explodingFourMonAdd中的lambda?或者以某种方式在>> =中进行curry 运算符使得折叠的行为类似于由>> =?

链接的函数列表

5 个答案:

答案 0 :(得分:22)

我认为您可以使用foldM

explodingFourSum numberList = foldM explodingFourAdd 0 numberList

这可以让你在开始时摆脱额外的lambda (只是0)。


BTW,请查看hoogle以搜索您不记得其名称的功能。

答案 1 :(得分:6)

  

基本上,有没有办法使用某种Monad折叠清理或消除explodingFourMonAdd中的lambda?

亚普。在Control.Monad中有foldM函数,这正是你想要的。因此,您可以将foldl的来电替换为foldM explodingFourAdd 0 numberList

答案 2 :(得分:5)

你可以利用Maybe是一个monad的事实。如果sequence :: [m a] -> m [a]m,则函数Maybe具有以下效果:如果某些Just x列表中的所有元素都为x,则结果为列表所有那些正义的。否则,结果为Nothing

所以你首先要确定所有元素,不管它是否失败。例如,举个例子:

foursToNothing :: [Int] -> [Maybe Int]
foursToNothing = map go where
  go 4 = Nothing
  go x = Just x

然后你运行序列和fmap折叠:

explodingFourSum = fmap (foldl' (+) 0) . sequence . foursToNothing

当然,您必须根据具体情况进行调整。

答案 3 :(得分:3)

这是其他人没有提到的另一种可能性。你可以单独检查四肢并做总和:

import Control.Monad
explodingFourSum xs = guard (all (/=4) xs) >> return (sum xs)

这是整个来源。这个解决方案在很多方面都很漂亮:它重用了很多已编写的代码,很好地表达了关于函数的两个重要事实(而这里发布的其他解决方案将这两个事实混合在一起)。

当然,至少有一个很好的理由来使用此实现。这里提到的其他解决方案只遍历输入列表一次;这与垃圾收集器很好地交互,在任何给定时间只允许列表的一小部分在内存中。另一方面,此解决方案遍历xs两次,这将阻止垃圾收集器在第一次传递期间收集列表。

答案 4 :(得分:2)

您也可以通过这种方式解决您的玩具示例:

import Data.Traversable

explodingFour 4 = Nothing 
explodingFour x = Just x

explodingFourSum = fmap sum . traverse explodingFour 

当然这只是因为一个值足以知道计算何时失败。如果失败条件取决于explodingFourSum两个值x和y,则需要使用foldM

BTW:写explodingFour的奇特方式是

import Control.Monad

explodingFour x = mfilter (/=4) (Just x)

这个技巧也适用于explodingFourAdd,但不太可读:

explodingFourAdd x y = Just (x+) `ap` mfilter (/=4) (Just y)