【问题标题】:Best practice how to evaluate a list of Maybes评估Maybes列表的最佳实践
【发布时间】:2011-12-17 11:05:56
【问题描述】:

我正在寻找一个函数,它接受一个函数 (a -> a -> a) 和一个 [Maybe a] 列表并返回 Maybe a。 Hoogle 没有给我任何有用的东西。这看起来是一种很常见的模式,所以我想问一下这种情况是否有最佳实践?

>>> f (+) [Just 3, Just 3]
Just 6
>>> f (+) [Just 3, Just 3, Nothing]
Nothing

提前致谢,克里斯

【问题讨论】:

  • 我不知道,这就是我问的原因。
  • 我已扩展我的答案以匹配您更新的问题。

标签: haskell monads


【解决方案1】:

您应该首先将[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 如何工作以实现所需的行为。 :)

然后您可以使用(&lt;$&gt;)(来自Control.Applicative;等效地,fmapliftM)将您的二进制函数折叠到列表中:

GHCi> foldl' (+) 0 <$> sequence [Just 1, Just 2]
Just 3

当然,你可以使用任何你想要的折叠,例如foldrfoldl1等。

另外,如果您希望列表为空时结果为Nothing,从而能够省略折叠的零值而不必担心空列表出现错误,那么您可以使用此折叠功能:

mfoldl1' :: (MonadPlus m) => (a -> a -> a) -> [a] -> m a
mfoldl1' _ [] = mzero
mfoldl1' f (x:xs) = return $ foldl' f x xs

同样适用于foldrfoldl 等。您需要为此导入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

【讨论】:

    【解决方案2】:

    据我了解,如果其中任何一个是 Nothing,您想要获得一堆可能或 Nothing 的总和。这其实很简单:

    maybeSum = foldl1 (liftM2 (+))
    

    您可以将其概括为:

    f :: Monad m => (a -> a -> a) -> [m a] -> m a
    f = foldl1 . liftM2
    

    当与Maybe monad 一起使用时,f 完全按照您想要的方式工作。

    如果你关心空列表,你可以使用这个版本:

    f :: MonadPlus m => (a -> a -> a) -> [m a] -> m a
    f _ []      = mzero
    f fn (x:xs) = foldl (liftM2 fn) x xs
    

    【讨论】:

    • 另一方面,1 后面是 2 有点可爱。但也许可爱并不是衡量代码质量的最佳指标...
    • 是:来自 MonadPlus 的 mzero,来自 Alternative 的 empty 或来自 Monoid 的 mempty(按优先顺序,IMO;MonadPlus 只是 Alternative 的旧副本,它依赖于 Monad 而不是 Applicative ,但 Monad 还不需要 Applicative,因此它避免了添加额外的约束,并且 Monoid 没有合适的类型在这里很好地工作)。
    • 啊,我没有意识到您的解决方案也可以适应使用 mzero。但是,我建议您在后备情况下内联 foldl1 的定义,因为现在它不必要地使用了部分函数,​​即使您知道列表不是 []
    • 根据foldl1的定义将f fn l = foldl1 (liftM2 fn) l转换为f fn (x:xs) = foldl (liftM2 fn) x xs;没什么大不了的,只是避免在不需要时调用部分函数。
    • 为空列表返回Nothing 对我来说似乎违反直觉,我更喜欢foldl 和零元素,否则它将混合两种不同的情况。顺便说一句,另一个变体是foldl1 (liftA2 (+)) [Just 1, Just 10, Just 100]
    【解决方案3】:

    那么简单的事情呢:

    λ Prelude > fmap sum . sequence $ [Just 1, Just 2]
    Just 3
    λ Prelude > fmap sum . sequence $ [Just 1, Just 2, Nothing]
    Nothing
    

    或者,使用(+)

    λ Prelude > fmap (foldr (+) 0) . sequence $ [Just 1, Just 2]
    Just 3
    λ Prelude > fmap (foldr (+) 0) . sequence $ [Just 1, Just 2, Nothing]
    Nothing
    

    所以,maybeSum = fmap sum . sequence

    【讨论】:

    • 刚刚注意到 ehird 的答案已经包含这个版本!在阅读ehird的回复时,一看到MonadPlus这个词,我虽然:“一定有更简单的方法”,我没有继续说:对不起!无论如何,我赞成 ehird 的回答。
    • 哈哈,没什么大不了的 :) 关于MonadPlus 的好点,我试图让它更清楚。 (不小心按住键删除了我之前的评论,哎呀……)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-14
    • 2019-07-10
    • 1970-01-01
    相关资源
    最近更新 更多