【问题标题】:MonadParallel Instance for RandRand 的 MonadParallel 实例
【发布时间】:2012-12-31 13:23:29
【问题描述】:

我目前正在ReaderT r (Rand StdGen) a 中处理我希望并行运行的计算。我遇到了Monad Parallel,它似乎会做我想做的事。

ReaderT 已经有一个 MonadParallel 实例,但我必须从 monad-randomRand 创建自己的实例。但是,我不确定我做对了。我对 Haskell 中的并行编程不太熟悉,但我相信在并行中运行计算应该给出与正常运行时相同的值。因为我的 Rand 的 bindM2 实例使用 split(因此从同一个初始生成器获得一组不同的随机数),所以我的实例不是这种情况。

instance P.MonadParallel (Rand StdGen) where
    bindM2 f ma mb = do
        split1 <- getSplit
        split2 <- getSplit
        let a = evalRand ma split1
        let b = evalRand mb split2
        a `par` b `pseq` f a b

虽然我觉得有理由忽略这一点(数字仍然是随机的,对吗?)我也忍不住觉得我错过了一些东西。这样可以吗?还是有更好的解决方案?

【问题讨论】:

    标签: haskell random parallel-processing


    【解决方案1】:

    我主要担心这违反了以下保证:

    除了可能的副作用排序,这个函数等价于\f ma mb-&gt; do {a &lt;- ma; b &lt;- mb; f a b}

    这些会有不同的结果:

    let g = mkStdGen 0
        r = evalRand (do x <- getRandom
                         y <- getRandom
                         return (x, y)) g
    

    let g = mkStdGen 0
        r = evalRand (P.bindM2 (\x y -> return (x,y)) getRandom getRandom) g
    

    这确实引发了关于split、伪随机数以及随机数在纯度方面的性质的有趣问题。我很难想象您的实例会产生不利影响的情况,但软件系统的复杂性从未停止让我感到惊讶。因此,即使是随机数,我也不会违反对bindM2 的期望。

    【讨论】:

    • 这是一个有趣的问题——如果不重新思考拆分的工作原理,可能无法解决。正如你所说,在我的情况下,这并不完全是一个问题,但它肯定会持续关注。
    【解决方案2】:

    MonadParallelbindM2 存在固有问题。它的文档说:

    并行执行两个一元计算;当它们都完成后,将结果传递给函数。 除了可能的副作用排序之外,这个函数等价于\f ma mb-&gt; do {a &lt;- ma; b &lt;- mb; f a b}

    问题在于,在单子计算中(与应用函子不同)值可能取决于效果,也取决于它们的顺序。所以这个要求没有多大意义。考虑

    do
      a <- getCurrentTime -- from Date.Time
      b <- getCurrentTime
      return (a <= b)
    

    这总是返回True,但如果您重新排序效果,它将开始返回False。 显然,通过并行化这两个计算,您会得到一个非常不确定的结果。

    所以很难解释bindM2的意图。我想说我们可以将MonadParallel 的实例分为两类:

    1. 真正具有确定性且bindM2 始终等于\f ma mb-&gt; do {a &lt;- ma; b &lt;- mb; f a b} 的那些。也就是说,效果的顺序不会改变。这些实现通常使用par 定义,不会改变程序的语义,只会并行运行一些部分。
    2. 那些确实依赖于效果排序,因此bindM2 可以与\f ma mb-&gt; do {a &lt;- ma; b &lt;- mb; f a b} 任意不同。似乎目前唯一这样的实例是IO,它使用forkIO 为其中一个计算生成一个新线程。

    因此,您是否接受 bindM2 作为有效实例取决于您如何解释文档。如果您认为 (2.) 无效,那么您的实现也是无效的(您也应该拒绝 IO)。如果您将 (2.) 解释为有效,则不必担心。

    【讨论】:

    • 这里的主要问题实际上是使用split 为 Rand 定义我的实例。它仍然是确定性的(据我所知)由于split 的工作方式,与顺序版本相比,它会产生不同的结果。但是,考虑到 IO 的默认实例,您会认为 MonadParallel 的使用通常不安全吗?
    • @TomSavage 我明白了。安全或不安全取决于您(或使用您的代码的人)的期望。我不认为MonadRandom 通常是不安全的,只是 id 不清楚bindM2 应该遵守什么。当有人期望一些不真实的东西时,它就开始变得不安全了。也许最安全的方法是创建自己的 monad(甚至可能是 monad 转换器),特别适合并行随机计算并记录其确切行为。恕我直言,这将是一个不错且有用的库。 (您当然可以在内部使用MonadRandom 来实现它。)
    • @TomSavage 如果您使用IOMonadRandom 实例,调用bindM2 的结果通常与按顺序调用动作不同。所以Rand StdGen的实例基本上和IO的一样正确。
    猜你喜欢
    • 2011-07-18
    • 2010-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-13
    • 2013-09-09
    • 1970-01-01
    • 2013-10-13
    相关资源
    最近更新 更多