【发布时间】:2012-08-14 14:34:55
【问题描述】:
我正在寻找一种 Haskell 设计,以某种方式组合一元操作链(通常是 IO),使后面的操作依赖于之前的操作,但在某些情况下可以在它们完成之前执行。
目前我想出的解决方案是:
type Future m a = m (m a)
读取:一个单子动作,它启动某个过程并返回一个动作,该动作将返回该过程的结果(可能通过等待该过程完成)。
所以在某个链中a >>= b >>= c b 得到一个返回 a 的结果的动作。如果 b 评估此操作,它会等待 a 完成,否则它将并行运行。这也意味着,如果某个动作不需要前一个动作的结果作为参数,则根据定义它不依赖于它,因此依赖关系是显式的。
一些示例代码:
date :: Future IO String -- long process to find out the date
date = do
print "attempting to get date" -- will usually start some thread or process to compute the date
return (print "today") -- will wait for this thread or process and return the computed date
main = do
d <- date -- starts recieving the date
print "foo" -- some other process
d >>= print -- waits until the date has been computed and prints it out
输出:
"attempting to get date"
"foo"
"today"
存在一个问题:如果一个动作决定等待前一个动作,它将始终依赖于之前的所有其他动作(在我的情况下)。但是在上面的例子中,如果 c 决定等待 b 但 b 没有决定等待 a,c 可能会在 a 完成之前开始,这不应该发生。
作为解决方案,我编写了另一个组合运算符:
(>=>) :: Monad m => Future m a -> (m a -> Future m b) -> Future m b
a >=> f = do
r1 <- a
r2 <- f r1
return (r1 >> r2)
所以这将结合“等待动作”和a >=> b >=> c 将正常工作,如果 c 等待 b 这个等待动作也将等待 a。
然而,这种方法还有另一个问题(除此之外,您需要记住使用 >=> 而不是 >>=):可能会多次评估等待操作。
如果 b 等待 a 并且 c 等待 b 则等待 b 将连接到等待 a,因此等待 a 将被执行两次。
实际问题出在 >=> 中:f r1 可能会评估 r1,在这种情况下,它不需要在 return 语句中使用 r2 进行排序(因为它已经被执行,因此 a 已经完成)。但它也可能不会,我不知道。
所以我基本上想要的正是这个,但不可能多次运行等待操作。不幸的是,我在功能设计方面不是很有经验。
所以我希望你能以某种方式启发我如何增强或改变我的设计,或者为我指出一种不同的、更灵活的方法。
编辑根据到目前为止的答案,我想对我真正想要的内容进行更多说明:
我不想推迟(甚至跳过)动作的执行,我也不需要线程或类似的并行功能。实际上我正在调用外部进程。一个例子是
backup :: Future IO ExitCode
backup = do
pid <- startProcess "backup"
return (waitForProcessAndGetExitCode pid)
当我现在链接 backup >=> otherAction 之类的操作时,otherAction 可以在备份运行的同时运行(总体上节省了很多时间)。但是 otherAction 可能需要完成备份,这种情况下它可以使用它的参数等待备份并检查是否成功。无论哪种方式,都必须执行备份。
我现在正在寻找一个不错的通用解决方案,最好不要绑定到 IO monad。
更新我找到了适合我的解决方案。我在下面的单独答案中对其进行了描述。
【问题讨论】:
-
您可能希望为新组合器选择一个不同的名称,because
>=>is already taken。 -
即使您不需要并行执行,线程(如某些答案中所述)可能是一个很好的解决方案。 Haskell 中的线程相当容易使用。
-
值得指出的是,对于足够惰性的 monad(例如惰性 State monad),您无需执行任何操作即可获得所需的语义。
-
@JohnL 这听起来很有趣。我想知道 Haskell 的懒惰是否会对我有所帮助,但我无法想出一些可行的方法。这是否也适用于我的约束,如果在
a >>= b >>= cc 等待 b 它也会自动等待 a? -
您可能会在Orc library 中找到灵感,它可用于协调外部流程。特别是,它有一个组合器
eagerly,它产生一个动作并返回一个句柄,该句柄可用于等待动作终止。