我有一些用C语言编写的函数,我从Haskell调用.这些功能返回IO (CInt)
.有时我想运行所有函数,无论它们返回什么,这很容易.为了示例代码,这是当前正在发生的事情的一般概念:
Prelude> let f x = print x >> return x Prelude> mapM_ f [0..5] 0 1 2 3 4 5 Prelude>
我得到了我想要的副作用,我不关心结果.但是现在我需要在第一个没有返回我想要的结果的项目之后立即停止执行.假设返回值为4或更高要求执行停止 - 那么我想要做的是:
Prelude> takeWhile (<4) $ mapM f [0..5]
这给了我这个错误:
:1:22: Couldn't match expected type `[b]' against inferred type `IO a' In the first argument of `mapM', namely `f' In the second argument of `($)', namely `mapM f ([0 .. 5])' In the expression: takeWhile (< 4) $ mapM f ([0 .. 5])
这对我来说很有意义 - 结果仍然包含在IO monad中,我不能只比较IO monad中包含的两个值.我知道这正是monad的目的 - 将结果链接在一起并在满足某个条件时丢弃操作 - 但在这种情况下是否有一种简单的方法来"封装"IO monad以在条件下停止执行链我选择的,没有写一个实例MonadPlus
?
为了takeWhile f
的目的,我可以"解除"价值吗?
这是算子适合的解决方案吗?Functors还没有和我"点击",但我觉得这可能是一个使用它们的好方法.
@sth对我想要的答案最接近 - 事实上,这几乎就是我想要的,但我仍然想看看是否有一个标准的解决方案没有明确的递归 - 这是Haskell,之后所有!回顾我如何措辞我的问题,现在我可以看到我对自己想要的行为不够清楚.
f
我上面用于示例的函数仅仅是一个例子.实际功能是用C语言编写的,专门用于副作用.我不能使用@Tom的建议,mapM_ f (takeWhile (<4) [0..5])
因为我不知道任何输入在执行之前是否真的会导致成功或失败.
我实际上并不关心返回的列表 - 我只想调用C函数,直到列表用完或第一个C函数返回失败代码.
在C风格的伪代码中,我的行为是:
do { result = function_with_side_effects(input_list[index++]); } while (result == success && index < max_index);
所以,@ sth的答案再次执行我想要的确切行为,除了结果可能(应该?)被丢弃.一个dropWhileM_
函数对我来说是等价的.为什么没有类似的功能或takeWhileM_
在Control.Monad中?我看到在邮件列表上有类似的讨论,但似乎没有任何结果.
您可以将序列定义为
sequence xs = foldr (liftM2 (:)) (return []) xs
这个问题liftM2
,你已经看到的是你没有机会停下m2
,这可能是launchTheMissiles
!
liftM2 :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c liftM2 f m1 m2 = do x1 <- m1 x2 <- m2 return (f x1 x2)
使用guard
如下所示似乎很有吸引力:
sequenceUntil p xs = foldr (myLiftM2 p (:)) (return []) xs where myLiftM2 p f m1 m2 = do x1 <- m1 guard $ p x1 x2 <- m2 return (f x1 x2)
上面的代码将在您的应用程序中失败,因为IO monad不是MonadPlus的实例.
所以握住它的手多一点
module Main where import Control.Monad printx :: Int -> IO Int printx x = do print x return x sequenceUntil :: (Monad m) => (a -> Bool) -> [m a] -> m [a] sequenceUntil p xs = foldr (myLiftM2 (:) []) (return []) xs where myLiftM2 f z m1 m2 = do x1 <- m1 if p x1 then do x2 <- m2 return $ f x1 x2 else return z main :: IO () main = do let as :: [IO Int] as = map printx [1..10] ys <- sequenceUntil (< 4) as print ys
即使as
是1到10之间的动作列表,输出也是如此
1 2 3 4 [1,2,3]
丢弃结果是微不足道的:
sequenceUntil_ :: (Monad m) => (a -> Bool) -> [m a] -> m () sequenceUntil_ p xs = sequenceUntil p xs >> return () main :: IO () main = do let as :: [IO Int] as = map printx [1..] sequenceUntil_ (< 4) as
注意使用[1..]
它表明新的组合子保持懒惰.
您可能更喜欢spanM
:
spanM :: (Monad m) => (a -> Bool) -> [m a] -> m ([a], [m a]) spanM _ [] = return ([], []) spanM p (a:as) = do x <- a if p x then do (xs,bs) <- spanM p as return (x:xs, bs) else return ([x], as)
请注意,它与span略有不同,因为它在结果列表中包含失败元素.这对是剩下的动作.例如:
*Main> (xs,bs) <- spanM (< 4) as 1 2 3 4 *Main> xs [1,2,3,4] *Main> sequence bs 5 6 7 8 9 10 [5,6,7,8,9,10]
另一种选择:
untilM :: Monad m => (a -> Bool) -> [m a] -> m () untilM p (x:xs) = do y <- x unless (p y) $ untilM p xs
请注意,谓词的意义是补充:
*Main> untilM (>= 4) as 1 2 3 4
我不认为takeWhileM
标准库中有类似的东西,但您可以自己编写,以便只执行所需的IO:
takeWhileM :: (Monad m) => (a -> Bool) -> [m a] -> m [a] takeWhileM _ [] = return [] takeWhileM p (a:as) = do v <- a if p v then do vs <- takeWhileM p as return (v:vs) else return []
提供的列表仅在找到与谓词不匹配的元素之前进行求值:
*Main> takeWhileM (<4) (map f [1..5]) 1 2 3 4 [1,2,3]
编辑:现在我看到你在寻找什么.
gbacon发布了一个很好的sequenceWhile
功能,几乎是你需要的"原始"功能.
实际上,既然你只对副作用感兴趣,sequenceWhile_
那就足够了.这是一个定义(再次受到gbacon的启发,投票给他!):
sequenceWhile_ :: (Monad m) => (a -> Bool) -> [m a] -> m () sequenceWhile_ p xs = foldr (\mx my -> mx >>= \x -> when (p x) my) (return ()) xs
你这样称呼:
Prelude Control.Monad> sequenceWhile (<4) $ map f [1..]
原始答案:
你不能只是"解除" IO
Monad中的值来使用takeWile
,但你可以"提升" takeWhile
在Monad中使用!
该liftM功能将一个函数(a -> b)
的功能(m a -> m b)
,在这里m
是一个单子.
(作为一个方面说明,您可以通过搜索的类型上找到这样的功能Hoogle通过搜索,在这种情况下:Monad m => (a -> b) -> (m a -> m b)
)
有了liftM
你可以这样做:
Prelude> :m + Control.Monad Prelude Control.Monad> let f x = print x >> return x Prelude Control.Monad> liftM (takeWhile (<4)) $ mapM f [0..5] 0 1 2 3 4 5 [0,1,2,3]
现在,这可能不是你想要的.在返回列表之前,mapM
将按f
顺序将函数应用于整个列表.然后将结果列表传递给提升的takeWhile
函数.
如果要在第三个元素后停止打印,则必须停止调用打印.这意味着,不要申请f
这样的元素.所以,你最终会得到一些简单的东西:
Prelude> mapM_ f (takeWhile (<4) [0..5])
顺便说一句,如果你想知道为什么 mapM
在返回列表之前首先打印所有内容.您可以通过将函数替换为其定义来查看:
mapM f [0..1] = sequence (map f [0..1]) = sequence (f 0 : map f [1..1]) = sequence (f 0 : f 1 : []) = sequence ((print 0 >> return 0) : f 1 : []) = sequence ((print 0 >> return 0) : (print 1 >> return 1) : []) = do x <- (print 0 >> return 0) xs <- (sequence ((print 1 >> return 1) : [])) return (x:xs) = do x <- (print 0 >> return 0) xs <- (do y <- (print 1 >> return 1) ys <- sequence ([]) return (y:ys)) return (x:xs) = do x <- (print 0 >> return 0) xs <- (do y <- (print 1 >> return 1) ys <- return [] return (y:ys)) return (x:xs) = do x <- (print 0 >> return 0) xs <- (do y <- (print 1 >> return 1) return (y:[])) return (x:xs) = do x <- (print 0 >> return 0) xs <- (print 1 >> return (1:[])) return (x:xs) = do x <- (print 0 >> return 0) print 1 return (x:1:[]) = do print 0 print 1 return (0:1:[])
将函数替换为其定义的过程称为等式推理.
如果我没有犯任何错误,你现在可以(希望)看到mapM
(使用sequence
)首先打印所有内容,然后返回一个列表.
您可以使用"List"包中的那个.
import Control.Monad.ListT (ListT) import Data.List.Class (execute, fromList, joinM, takeWhile) import Prelude hiding (takeWhile) f x = print x >> return x main = execute . takeWhile (< 4) . joinM $ fmap f (fromList [0..5] :: ListT IO Int)
fromList [0..5]
创建一个包含0..5的monadic列表,该列表不执行任何monadic操作
fmap f
到那个列表导致一个ListT IO (IO Int)
仍然没有执行monadic动作,只包含一个.
joinM
把它变成了一个ListT IO Int
.当项目被消耗时,每个包含的动作都将被执行,其结果将是列表中的值.
takeWhile
对任何一个都是概括的List
.两者[]
和" Monad m => ListT m
"都是实例List
.
execute
使用monadic列表,执行其所有操作.
如果您对可以使用的结果感兴趣"toList :: List m => m a -> ItemM m [a]"
(" ItemM (ListT IO)
"是IO
).所以在这种情况下它是" toList :: ListT IO a -> IO [a]
".更好的是,您可以继续使用诸如scanl
等的高阶函数来处理正在执行的monadic列表.