我有一些用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
的实例?
为了takeWhile的目的,我可以 f
来自 f
的值吗?
这是算子适合的解决方案吗? Functors还没有和我“点击”,但我觉得这可能是一个使用它们的好方法 .
Update:
@sth对我想要的答案最接近 - 实际上,'s almost exactly what I was going for, but I'仍然想知道是否有一个标准的解决方案对我理想的行为不够清楚 .
我上面用于示例的 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_
?我看到有a similar discussion on a mailing list,但似乎没有任何结果 .
5 回答
您可以将sequence定义为
liftM2的问题,你've been seeing is you don'有机会停止
m2
,这可能是launchTheMissiles
!如下所示使用guard似乎很吸引人:
上面的代码将在您的应用程序中失败,因为IO monad不是MonadPlus的实例 .
所以握住它的手多一点
即使
as
是超过1到10的动作列表,输出也是如此丢弃结果是微不足道的:
注意使用
[1..]
显示新的组合子maintains laziness .您可能更喜欢
spanM
:请注意,它与span稍有不同,因为它包含结果列表中的失败元素 . 这对是剩下的动作 . 例如:
另一种选择:
请注意,谓词的意义是补充:
我不认为标准库中有
takeWhileM
之类的东西,但您可以自己编写,以便只执行所需的IO:提供的列表仅在找到与谓词不匹配的元素之前进行求值:
Edit: 现在我看到你在找什么 .
gbacon发布了一个很好的
sequenceWhile
函数,这几乎就是你需要的"primitive" .实际上,既然你只对副作用感兴趣,那么_3042630就足够了 . 这是一个定义(再次受到gbacon的启发,投票给他!):
你这样称呼:
Original answer:
你不能只使用
IO
Monad的"unlift"值与takeWile
一起使用,但你可以在Monad中使用"lift"takeWhile
!liftM函数将函数
(a -> b)
带到函数(m a -> m b)
,其中m
是Monad .(作为旁注,您可以通过在Hoogle上搜索其类型来找到这样的函数,在这种情况下通过搜索:Monad m => (a -> b) -> (m a -> m b))
使用
liftM
,您可以这样做:现在,这可能不是你想要的 .
mapM
将f
函数应用于整个列表序列,在返回列表之前 . 然后将结果列表传递给提升的takeWhile
函数 .如果要在第三个元素后停止打印,则'll have to stop calling print. That means, don' t将
f
应用于此类元素 . 所以,你最终会得到一些简单的东西:顺便说一句,如果你想知道为什么
mapM
会在返回列表之前首先打印所有内容 . 您可以通过将函数替换为其定义来查看:将函数替换为其定义的过程称为等式推理 .
如果我没有犯任何错误,你现在(希望)可以看到
mapM
(使用sequence
)首先打印所有内容,然后返回一个列表 .您可以使用"List"包中的那个 .
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列表 .最近,您可以使用包含handy functions的MonadList hackage,如takeWhileM,dropWhileM,deleteByM等等 .