我正在寻找一个基本上类似于mapM
的函数 - 它执行一系列monadic动作,将列表中的每个值作为参数 - 每个monadic函数返回{{1} }。但是,我希望它在导致函数返回m (Maybe b)
值的第一个参数之后停止,之后不再执行,并返回该值。
嗯,只显示类型签名可能更容易:
Just
其中b是第一个findM :: (Monad m) => (a -> m (Maybe b)) -> [a] -> m (Maybe b)
值。结果中的Just
来自Maybe
(如果是空列表等),并且与Monadic函数返回的find
无关。
我似乎无法通过直接应用库函数来实现它。我可以用
Maybe
哪个会起作用,但我测试了这个,似乎在调用findM f xs = fmap (fmap fromJust . find isJust) $ mapM f xs
之前执行了所有的monadic动作,所以我不能在这里依赖懒惰。
find
实现此函数的最佳方法是什么,在第一次“just”返回后不会执行monadic操作?可以做的事情:
ghci> findM (\x -> print x >> return (Just x)) [1,2,3]
1
2
3
-- returning IO (Just 1)
甚至,理想情况下,
ghci> findM (\x -> print x >> return (Just x)) [1,2,3]
1
-- returning IO (Just 1)
希望有一个答案不使用显式递归,如果可能的话,它是库函数的组合?或者甚至是无点一个?
答案 0 :(得分:17)
一个简单的无点解决方案是使用MaybeT
变换器。每当我们看到m (Maybe a)
时,我们都可以将其包装到MaybeT
中,并立即获得所有MonadPlus
个函数。由于mplus
的{{1}}完全符合我们的需要 - 只有当第一个导致MaybeT
- Nothing
完全符合我们的需要时,它才会运行第二个给定的操作:
msum
更新:在这种情况下,我们很幸运存在一个monad转换器(import Control.Monad
import Control.Monad.Trans.Maybe
findM :: (Monad m) => (a -> m (Maybe b)) -> [a] -> m (Maybe b)
findM f = runMaybeT . msum . map (MaybeT . f)
),MaybeT
只有我们需要的语义。但在一般情况下,可能无法构建这样的变压器。 mplus
有一些法律必须满足其他monadic操作。但是,一切都没有丢失,因为我们实际上不需要MonadPlus
,我们所需要的只是一个合适的monoid折叠。
所以让我们假设我们没有(不能)拥有MonadPlus
。计算某些操作序列的第一个值由First
幺半群描述。如果左边的部分有值,我们只需要制作一个不会执行正确部分的monadic变体:
MaybeT
这个monoid恰好描述了这个过程,没有任何对列表或其他结构的引用。现在我们只使用这个monoid折叠列表:
newtype FirstM m a = FirstM { getFirstM :: m (Maybe a) }
instance (Monad m) => Monoid (FirstM m a) where
mempty = FirstM $ return Nothing
mappend (FirstM x) (FirstM y) = FirstM $ x >>= maybe y (return . Just)
此外,它允许我们使用findM' :: (Monad m) => (a -> m (Maybe b)) -> [a] -> m (Maybe b)
findM' f = getFirstM . mconcat . map (FirstM . f)
创建更通用(甚至更短)的功能:
Data.Foldable
答案 1 :(得分:9)
如果你不介意递归,我喜欢Cirdec的答案,但我认为相当于基于折叠的答案非常漂亮。
findM f = foldr test (return Nothing)
where test x m = do
curr <- f x
case curr of
Just _ -> return curr
Nothing -> m
一个很好的小测试,你对折叠的理解程度如何。
答案 2 :(得分:6)
这应该这样做:
findM _ [] = return Nothing
findM filter (x:xs) =
do
match <- filter x
case match of
Nothing -> findM filter xs
_ -> return match
如果你真的想免费点(添加为编辑)
以下内容将使用Alternative
仿函数在列表中找到一些内容,使用jozefg的答案中的折叠
findA :: (Alternative f) => (a -> f b) -> [a] -> f b
findA = flip foldr empty . ((<|>) .)
我不能让(Monad m) => m . Maybe
成为Alternative
的实例,但我们可以假装现有的功能:
-- Left biased choice
(<||>) :: (Monad m) => m (Maybe a) -> m (Maybe a) -> m (Maybe a)
(<||>) left right = left >>= fromMaybe right . fmap (return . Just)
-- Or its hideous points-free version
(<||>) = flip ((.) . (>>=)) (flip ((.) . ($) . fromMaybe) (fmap (return . Just)))
然后我们可以与findM
findA
findM :: (Monad m) => (a -> m (Maybe b)) -> [a] -> m (Maybe b)
findM = flip foldr (return Nothing) . ((<||>) .)
答案 3 :(得分:5)
这可以用MaybeT
monad变换器和Data.Foldable
很好地表达。
import Data.Foldable (msum)
import Control.Monad.Trans.Maybe (MaybeT(..))
findM :: Monad m => (a -> m (Maybe b)) -> [a] -> m (Maybe b)
findM f = runMaybeT . msum . map (MaybeT . f)
如果您更改搜索功能以生成MaybeT
堆栈,它会变得更好:
findM' :: Monad m => (a -> MaybeT m b) -> [a] -> MaybeT m b
findM' f = msum . map f
或无点:
findM' = (.) msum . map
原始版本也可以完全无点,但它变得非常难以理解:
findM = (.) runMaybeT . (.) msum . map . (.) MaybeT