为了学习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
运算符使得折叠的行为类似于由>> =?
答案 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)