哈斯克尔:monadic takeWhile?
我有一些用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]
这给了我这个错误:
<interactive>: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
的实例?
我可以为了拍摄的目的而“解除” f
的值吗?
这是一个功能适合的解决方案吗? 调度员还没有“点击”我,但我有一种印象,这可能是一个很好的情况下使用它们。
更新:
@sth与我想要的有最接近的答案 - 事实上,这几乎就是我所要做的,但我仍然想看看是否存在不是显式递归的标准解决方案 - 这是Haskell,后所有! 回顾一下我如何解释我的问题,现在我可以看到我对自己期望的行为不够清楚。
上面举例说明的f
函数仅仅是一个例子。 真正的功能是用C编写的,专门用于它们的副作用。 我不能使用mapM_ f (takeWhile (<4) [0..5])
对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_
函数对我来说是等价的。 为什么在Control.Monad中没有这样的函数或takeWhileM_
? 我发现在邮件列表上有类似的讨论,但似乎没有任何结果。
您可以将序列定义为
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)
请注意,它与跨度略有不同,因为它包含结果列表中的失败元素。 这一对是剩下的动作。 例如:
*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函数将对函数(ma -> mb)
执行函数(a -> b)
(ma -> mb)
,其中m
是Monad。
(作为一个附注,你可以通过在Hoogle上搜索它的类型来找到这样的函数,在这种情况下通过搜索: Monad m => (a -> b) -> (ma -> mb)
)
随着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
将按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
)首先打印所有内容,然后返回一个列表。