Haskell:单子 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
的实例?
为了 takeWhile 的目的,我可以直接从 f
中“取消”值吗?
这是函子适合的解决方案吗? 函子还没有与我“合拍”,但我有这样的印象:这可能是使用它们的好情况。
Update:
@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_ 函数是等效的。 为什么 Control.Monad 中没有类似的函数或 takeWhileM_
? 我看到邮件列表上有类似的讨论,但似乎没有任何结果。
I have some functions written in C that I call from Haskell. These functions return IO (CInt)
. Sometimes I want to run all of the functions regardless of what any of them return, and this is easy. For sake of example code, this is the general idea of what's happening currently:
Prelude> let f x = print x >> return x
Prelude> mapM_ f [0..5]
0
1
2
3
4
5
Prelude>
I get my desired side effects, and I don't care about the results. But now I need to stop execution immediately after the first item that doesn't return my desired result. Let's say a return value of 4 or higher requires execution to stop - then what I want to do is this:
Prelude> takeWhile (<4) $ mapM f [0..5]
Which gives me this error:
<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])
And that makes sense to me - the result is still contained in the IO monad, and I can't just compare two values contained in the IO monad. I know this is precisely the purpose of monads -- chaining results together and discarding operations when a certain condition is met -- but is there an easy way to "wrap up" the IO monad in this case to stop executing the chain upon a condition of my choosing, without writing an instance of MonadPlus
?
Can I just "unlift" the values from f
, for the purposes of the takeWhile?
Is this a solution where functors fit? Functors haven't "clicked" with me yet, but I sort of have the impression that this might be a good situation to use them.
Update:
@sth has the closest answer to what I want - in fact, that's almost exactly what I was going for, but I'd still like to see whether there is a standard solution that isn't explicitly recursive -- this is Haskell, after all! Looking back on how I worded my question, now I can see that I wasn't clear enough about my desired behavior.
The f
function I used above for an example was merely an example. The real functions are written in C and used exclusively for their side effects. I can't use @Tom's suggestion of mapM_ f (takeWhile (<4) [0..5])
because I have no idea whether any input will really result in success or failure until executed.
I don't actually care about the returned list, either -- I just want to call the C functions until either the list is exhausted or the first C function returns a failure code.
In C-style pseudocode, my behavior would be:
do {
result = function_with_side_effects(input_list[index++]);
} while (result == success && index < max_index);
So again, @sth's answer performs the exact behavior that I want, except that the results may (should?) be discarded. A dropWhileM_
function would be equivalent for my purposes. Why isn't there a function like that or takeWhileM_
in Control.Monad? I see that there was a similar discussion on a mailing list, but it appears that nothing has come of that.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
您可以定义序列 作为
liftM2
你一直看到的是你没有机会阻止m2
,这可能是launchTheMissiles
!使用
guard
如下所示似乎很有吸引力:
上面的代码将在您的应用程序中失败,因为 IO monad 不是 MonadPlus。
因此,握紧它的手一点
即使
as
是超过 1 到 10 的操作列表,输出是丢弃结果然后微不足道:
请注意
[1..]< 的使用/code> 显示新的组合器保持惰性。
您可能更喜欢
spanM
:请注意,它与 span ,因为它在结果列表中包含失败的元素。 两人的第二个任务是剩下的行动。 例如:
另一种选择:
注意谓词的含义是补充的:
You might define sequence as
The problem with
liftM2
that you've been seeing is you don't have an opportunity to stopm2
, which might belaunchTheMissiles
!Using
guard
as in the following seems appealing:The code above will fail in your application because the IO monad is not an instance of MonadPlus.
So hold its hand a little more
Even though
as
is a list of actions over 1 to 10, the output isDiscarding the results is then trivial:
Note the use of
[1..]
that shows the new combinator maintains laziness.You may prefer
spanM
:Note that it differs slightly from span in that it includes the failing element in the result list. The pair's second is the remaining actions. For example:
Yet another alternative:
Note that the sense of the predicate is complemented:
我认为标准库中没有类似
takeWhileM
的东西,但您可以自己编写它,以便只执行所需的 IO:仅对提供的列表进行评估,直到有一个元素出现为止。发现,与谓词不匹配:
I don't think there is anything like a
takeWhileM
in the standard library, but you could write it yourself so that only as much IO as needed is executed:The supplied list is only evaluated until an element is found, that doesn't match the predicate:
编辑:现在我明白你在寻找什么了。
gbacon 发布了一个很好的
sequenceWhile
函数,这几乎是您需要的“原始”函数。实际上,由于您只对副作用感兴趣,因此
sequenceWhile_
应该足够了。 这是一个定义(再次受到 gbacon 的启发,投票给他!):您可以这样称呼:
原始答案:
您不能只是从
IO 中“取消”值 Monad 与
takeWile
一起使用,但您可以“提升”takeWhile
以在 Monad 中使用!liftM 函数将将函数
(a -> b)
转换为函数(ma -> mb)
,其中m
是 Monad。(顺便说一句,您可以通过在 Hoogle 上搜索其类型来找到这样的函数,在此案例通过搜索:
Monad m => (a -> b) -> (ma -> mb)< /code>
)
使用
liftM
你可以做到这一点:现在,这可能不是你想要的。 在返回列表之前,
mapM
会将f
函数按顺序应用于整个列表。 然后将结果列表传递给提升的 takeWhile 函数。如果您想在第三个元素之后停止打印,则必须停止调用 print。 这意味着,不要将
f
应用于此类元素。 因此,您最终会得到一些简单的结果,例如:顺便说一下,您是否想知道为什么
mapM
将在返回列表之前首先打印所有内容。 您可以通过用函数的定义替换函数来看到这一点:用函数的定义替换函数的过程称为等式推理。
如果我没有犯任何错误,您现在(希望)可以看到
mapM
(使用sequence
)首先打印所有内容,然后然后返回一个列表。Edit: Now I see what you're looking for.
gbacon posted a nice
sequenceWhile
function, which is almost the "primitive" you need.Actually, since you're only interested in the side effects,
sequenceWhile_
should be enough. Here's a definition (again, inspired by gbacon, vote him up!):You call this like so:
Original answer:
You can't just "unlift" the values from the
IO
Monad for use withtakeWile
, but you can "lift"takeWhile
for use within a Monad!The liftM function will take a function
(a -> b)
to a function(m a -> m b)
, wherem
is a Monad.(As a side note, you can find a function like this by searching for its type on Hoogle, in this case by searching for:
Monad m => (a -> b) -> (m a -> m b)
)With
liftM
you can do this:Now, this might not be what you wanted. The
mapM
will apply thef
function to the entire list in sequence, before returning a list. That resulting list is then passed to the liftedtakeWhile
function.If you want to stop printing after the third element, you'll have to stop calling print. That means, don't apply
f
to such an element. So, you'll end up with something simple like:By the way, should you wonder why
mapM
will first print everything, before returning the list. You can see this by replacing the functions with their definitions:This process of replacing functions with their definitions is called equational reasoning.
If I didn't make any mistakes, you can now (hopefully) see that
mapM
(usingsequence
) first prints everything, and then returns a list.您可以使用 "List" 包中的一个。
fromList [0..5]
创建一个包含 0..5 的单子列表,该列表不执行单子操作fmap f
到该列表会产生ListT IO (IO Int)
仍然不执行单子操作,只包含单子操作。joinM
将其转换为ListT IO Int
。 当该项目被消耗时,每个包含的操作都会被执行,其结果将是列表中的值。takeWhile
泛化于任何List
。[]
和“Monad m => ListT m
”都是List
的实例。execute
使用单子列表,执行其所有操作。"toList :: List m => ma -> ItemM m [a]"
("ItemM (ListT IO)
”是IO
)。 所以在本例中它是“toList :: ListT IO a -> IO [a]
”。 更好的是,您可以继续使用高阶函数(例如scanl
等)在执行时处理单子列表。You can use the one from the "List" package.
fromList [0..5]
creates a monadic list containing 0..5 which performs no monadic actionsfmap f
to that list results in aListT IO (IO Int)
which still performs no monadic actions, just contains ones.joinM
turns that into aListT IO Int
. every contained action would get executed when the item is consumed and its result will be the value in the list.takeWhile
is generalized for anyList
. Both[]
and "Monad m => ListT m
" are instances ofList
.execute
consumes the monadic list, executing all its actions."toList :: List m => m a -> ItemM m [a]"
("ItemM (ListT IO)
" isIO
). so in this case it's "toList :: ListT IO a -> IO [a]
". Better yet you can keep using higher-order functions such asscanl
, etc to process the monadic list as it is being executed.最近,您可以使用包含 方便的函数,如 takeWhileM、dropWhileM、deleteByM 等等。
More recently, you can use the MonadList hackage that includes handy functions like takeWhileM, dropWhileM, deleteByM and many more.