您想要:
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
类型的作用吧。
tryPick f = foldr (mplus . f) mzero :: (MonadPlus m, Foldable t) => (a -> m b) -> t a -> m b
,两全其美! - J. AbrahamsonData.Maybe
中的 listToMaybe
函数看起来很不错:
tryPick f = listToMaybe . mapMaybe f
\(x:xs) -> case (f x) : tryPick f xs of { [] -> Nothing; (y:_) -> Just y }
。 - J. Abrahamson这并不一定是最简单的解决方案,但我认为强调基于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 }
First
是Maybe
的一个特化版本,它具有一个mappend
操作,用于选择“第一个”或“最左边”的Just
。
因此,将它们组合起来,getFirst . foldMap (First . f)
会在[a]
中计算您的(a -> Maybe b)
函数,并使用“第一个”Just
获胜的规则将结果压缩在一起。
Alt
和getAlt
是First
和getFirst
更现代的替代品。 - Joseph Sible-Reinstate Monica我来参加派对有点晚,但这里有一个变化版的 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!