【问题标题】:Folding across Maybes in Haskell在 Haskell 中折叠 Maybes
【发布时间】:2011-09-18 20:22:07
【问题描述】:

在尝试学习 Haskell 时,我遇到了一种情况,我希望对列表进行折叠,但我的累加器是 Maybe。然而,我正在折叠的函数接受了 Maybe 中的“提取”值,如果一个失败,它们都会失败。我有一个我觉得很笨拙的解决方案,但是我对 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?或者以某种方式在 >>= 中进行柯里化 运算符,以便折叠的行为类似于由 >>=?

链接的函数列表

【问题讨论】:

  • 顺便说一句,模块 Maybe 已弃用。如果可能,请使用Data.Maybe

标签: haskell monads


【解决方案1】:

我觉得你可以用foldM

explodingFourSum numberList = foldM explodingFourAdd 0 numberList

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


顺便说一句,请查看 hoogle 以搜索您不记得名称的函数。

【讨论】:

  • 是的,foldM explodingFourAdd 0 应该完全一样。这种事情就是foldM 的用途。
  • @C. A. McCann:墨菲定律阻止我在没有口译员的情况下如此确定。也就是说,我现在正在编辑我的答案:)
  • @missingno:我几乎总是在发布答案之前在 GHCi 中进行测试。在这种情况下我很确定,因为你在我测试我的时候写下了你的答案。 ;] 我已经学会了先检查的艰难方法,尤其是在涉及一些单子提升功能的地方。 foldM 实际上不是我通常想要的单子折叠(有时会忘记认为它是)。
【解决方案2】:

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

雅普。在 Control.Monad 中有 foldM 函数,这正是你想要的。所以你可以用foldM explodingFourAdd 0 numberList替换你对foldl的调用。

【讨论】:

    【解决方案3】:

    您可以利用 Maybe 是一个单子这一事实。函数sequence :: [m a] -> m [a] 具有以下效果,如果mMaybe: 如果列表中的所有元素都是Just x 对于某些x,则结果是所有这些justs 的列表。否则,结果为Nothing

    所以你首先决定所有元素,是否失败。举个例子:

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

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

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

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

    【讨论】:

    • 在第二次阅读时,我非常喜欢这个解决方案:它将“检查四人”阶段与“求和”阶段分开,而且看起来它有机会与垃圾收集器很好地配合.凉爽的!我会将sequence . map 更改为mapM,不过:explodingFourSum = fmap sum . mapM (ensure (/=4))。 (参见ensure 的定义。)
    【解决方案4】:

    这是其他人未提及的另一种可能性。您可以单独检查四位数并求和:

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

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

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

    【讨论】:

      【解决方案5】:

      你也可以这样解决你的玩具例子:

      import Data.Traversable
      
      explodingFour 4 = Nothing 
      explodingFour x = Just x
      
      explodingFourSum = fmap sum . traverse explodingFour 
      

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

      顺便说一句:写explodingFour 的一种奇特方式是

      import Control.Monad
      
      explodingFour x = mfilter (/=4) (Just x)
      

      这个技巧也适用于explodingFourAdd,但可读性较差:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-08-30
        • 1970-01-01
        • 2023-04-09
        • 1970-01-01
        • 2019-02-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多