放松monadic计算中的排序约束

时间:2011-05-05 12:43:15

标签: haskell monads commutativity

这里有一些值得思考的东西。

当我编写monadic代码时,monad会对完成的操作进行排序。例如,如果我写入IO monad:

do a <- doSomething
   b <- doSomethingElse
   return (a + b)

我知道doSomething将在doSomethingElse之前执行。

现在,考虑C语言中的等效代码:

return (doSomething() + doSomethingElse());

C的语义实际上没有指定这两个函数调用将被评估的顺序,因此编译器可以随意移动它。

我的问题是:我如何在Haskell中编写monadic代码,这也会使得评估顺序未定义?理想情况下,当编译器的优化器查看代码并开始移动时,我会获得一些好处周围。

以下是一些可能无法完成工作的技术,但却是正确的“精神”:

  • functorial style 编写代码,即写plus doSomething doSomethingElse并让plus安排monadic调用。缺点:您失去了对monadic操作结果的共享,plus仍然决定何时最终会对事物进行评估。
  • 使用 lazy IO ,即unsafeInterleaveIO,它将计划推迟到评估的懒惰需求。但懒惰与严格的未定义顺序不同:特别是我希望我的所有monadic动作都能被执行。
  • 懒惰的IO,结合立即seq'ing所有的参数。特别是,seq不会强加排序约束。

从这个意义上讲,我想要一些比一元顺序更灵活的东西,但是比完全懒惰更不灵活。

3 个答案:

答案 0 :(得分:16)

过度顺序化monad代码的问题称为"commutative monads problem"

  

交换monad是monad,其行为顺序没有区别(他们通勤),即遵循代码:

do a <- f x
   b <- g y
   m a b

与:

相同
do b <- g y
   a <- f x
   m a b

有许多monad通勤(例如MaybeRandom)。如果monad是可交换的,则例如可以并行计算在其中捕获的操作。它们非常有用!

但是,我们对于通勤的monad没有良好的语法,虽然a lot of people have asked对于这样的事情 - 它仍然是open research problem

顺便说一句,应用函子确实给了我们重新排序计算的自由,但是,你必须放弃bind的概念(作为例如liftM2显示的建议)。

答案 1 :(得分:9)

这是一个深刻的肮脏黑客,但它似乎应该对我有所帮助。

{-# OPTIONS_GHC -fglasgow-exts #-}
{-# LANGUAGE MagicHash #-}
module Unorder where
import GHC.Types

unorder :: IO a -> IO b -> IO (a, b)
unorder (IO f) (IO g) = IO $ \rw# ->
           let (# _, x #) = f rw#
               (# _, y #) = g rw#
           in (# rw# , (x,y) #)

由于这会将非确定性置于编译器的手中,因此它也应该在控制流问题(即异常)方面表现得“正确”(即非确定性)。

另一方面,我们不能使用StateEither a这样的大多数标准monad相同的技巧,因为我们真的依赖于通过弄乱{{ {1}}令牌。为了获得正确的行为,我们需要一些优化器可用的注释,表明我们可以选择两个非等价的替代方案之间的非确定性选择。

答案 2 :(得分:3)

  

C的语义实际上没有指定这两个函数调用将被评估的顺序,因此编译器可以随意移动它。

但如果doSomething()导致副作用会改变doSomethingElse()的行为怎么办?你真的希望编译器搞乱订单吗? (提示:不)你在monad中的事实表明情况可能就是这样。你注意到“你失去了对结果的分享”也表明了这一点。

但是,请注意monadic并不总是意味着测序。这并不完全是您描述的内容,但您可能对Par monad感兴趣,它允许您并行执行您的操作。

您有兴趣保留未定义的顺序,以便编译器可以为您神奇地优化它。也许您应该使用像Par monad这样的东西来表示依赖关系(某些事情不可避免地要先发生在其他事情之前),然后让其余部分并行运行。

旁注:不要将Haskell的return混淆为类似C的return