在Haskell中实现“findM”?

时间:2013-09-01 02:39:27

标签: haskell

我正在寻找一个基本上类似于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)

希望有一个答案不使用显式递归,如果可能的话,它是库函数的组合?或者甚至是无点一个?

4 个答案:

答案 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