标准的 Haskell 函数 :: (a -> Maybe b) -> [a] -> Maybe b。

13

在F#中有一个标准的tryPick函数,如果列表中的某个元素应用该函数后成功,则返回该元素(从左到右)。我希望在Haskell中也有这样的标准函数。我使用了Hoogle搜索,但没有找到类似的函数。

我是Haskell的新手,不确定正确的做法是什么。您会像这样做吗:

tryPick:: (a -> Maybe b) -> [a] -> Maybe b
tryPick try xs = case Maybe.mapMaybe try xs of
    [] -> Nothing
    (x:_) -> Just x

?


Data.Maybe 中的一个函数已经可以处理你所写的情况了。 - Josh Lee
4个回答

16

您想要:

tryPick :: (a -> Maybe b) -> [a] -> Maybe b
tryPick f as = msum (map f as)

我将解释这是如何工作的。

map f as 会生成一个可能尝试的 Maybe 操作列表:

map f as :: [Maybe b]

msum 会逐个尝试操作,直到成功为止(返回值为 Just),或全部失败(返回值为 Nothing)。例如:

> msum [Nothing, Just 2, Just 3, Nothing]
Just 2
> msum [Nothing, Nothing]
Nothing

注意,msum的类型更为通用,因此我们可以将签名泛化为:

tryPick :: (MonadPlus m) => (a -> m b) -> [a] -> m b

现在这将适用于任何MonadPlus。尝试发现它对其他MonadPlus类型的作用吧。


1
tryPick f = foldr (mplus . f) mzero :: (MonadPlus m, Foldable t) => (a -> m b) -> t a -> m b,两全其美! - J. Abrahamson
从4.8.0.0版本开始,msum就是asum,专门用于MonadPlus。我个人今天会使用asum而不是msum。 - Joseph Sible-Reinstate Monica

14

Data.Maybe 中的 listToMaybe 函数看起来很不错:

tryPick f = listToMaybe . mapMaybe f

这会导致映射整个列表的额外内存开销吗?我不确定 GHC 是否会为其余计算创建一个 thunk 或者优化它。 - daniel gratzer
1
@jozefg它在给定无限列表时终止,因此从直觉上讲,它是可以的。 - Josh Lee
@jozefg 我也这么认为。在脑海中内联,我们会得到 \(x:xs) -> case (f x) : tryPick f xs of { [] -> Nothing; (y:_) -> Just y } - J. Abrahamson
1
我知道它终止了,我只是好奇它是否会产生额外的惰性计算(thunk),或者它是否被优化掉了。这不是什么大问题,只是一种好奇心。顺便说一下,比我的解决方案更清晰得到+1。 - daniel gratzer
这个程序在多个成功的应用中,返回的是一个成功应用的列表,而不仅仅是第一个成功应用吗? - Leif Willerts

12

这并不一定是最简单的解决方案,但我认为强调基于First Monoid的解决方案很重要。我认为这是最漂亮的方案。

import Data.Monoid
import Data.Foldable (Foldable, foldMap)

tryPick :: (a -> Maybe b) -> [a] -> Maybe b
tryPick f = getFirst . foldMap (First . f)     -- this is just `foldMap f`
                                               -- with the "firsty" Maybe Monoid

这同样可以立即推广到任何具有完全相同代码的Foldable

tryPick :: Foldable t => (a -> Maybe b) -> t a -> Maybe b

Foldable 实例提供了使用 Monoid 来“合并”所有元素的方法。其中,First 是一个被定义为 Monoid 的类型。

newtype First a = First { getFirst :: Maybe a }

FirstMaybe的一个特化版本,它具有一个mappend操作,用于选择“第一个”或“最左边”的Just

因此,将它们组合起来,getFirst . foldMap (First . f)会在[a]中计算您的(a -> Maybe b)函数,并使用“第一个”Just获胜的规则将结果压缩在一起。


AltgetAltFirstgetFirst更现代的替代品。 - Joseph Sible-Reinstate Monica

8

我来参加派对有点晚,但这里有一个变化版的 J. Abrahamson 的答案,它使用了 Conor McBride 美妙的 ala' 函数,该函数来自于 newtype:

import Control.Newtype (ala')
import Data.Foldable (Foldable, foldMap)
import Data.Monoid (First(..))

tryPick :: (Foldable t) => (a -> Maybe b) -> t a -> Maybe b
tryPick = ala' First foldMap

这可能看起来有点神秘,但我觉得它的解耦方式相当漂亮,将“集合容器”(First)与“集合方案”(foldMap)以及二者之间的“预处理函数”(a -> Maybe b)分开处理,同时隐藏了newtype的封装和解封。据我的经验,ala是一个创建优美代码的好工具,我想为它做些宣传。感谢Conor!

这是我长时间以来看到的最棒的答案。我刚学到了非常有用的东西。谢谢! - comonad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接