MonadParallel Instance for Rand

I'm currently working with computations in ReaderT r (Rand StdGen) a which I would like to run in parallel. I've come across Monad Parallel which seems like it will do what I want.

There is already an instance of MonadParallel for ReaderT but I had to create my own for Rand from monad-random. However, I'm not sure I've done it right. I'm not too familiar with parallel programming in Haskell, but I believe that there is an expectation that running compuations in parrallel should give the same value as when they're run normally. Because my instance of bindM2 for Rand uses split (and therefore gets a different set of random numbers from the same initial generator) this isn't the case for my instance.

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

While I feel that there is a case for ignoring this (the numbers are still random, right?) I also can't help feeling that I'm missing something. Is this okay or is there a better solution?


My main concern is that this violates the guarantee that:

Apart from the possible ordering of side effects, this function is equivalent to f ma mb-> do {a <- ma; b <- mb; fab} f ma mb-> do {a <- ma; b <- mb; fab}

These will have different results:

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

vs

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

This does raise interesting questions about split , pseudorandom numbers, and the nature of random numbers in regards to purity. It's hard for me to imagine a case where your instance would have adverse affects, but the complexity of software systems has never stopped surprising me. And because of that, I wouldn't violate an expectation about bindM2 , even if it is with random numbers.


There is an inherent problem in MonadParallel 's bindM2 . Its documentation says:

Perform two monadic computations in parallel; when they are both finished, pass the results to the function. Apart from the possible ordering of side effects, this function is equivalent to f ma mb-> do {a <- ma; b <- mb; fab} f ma mb-> do {a <- ma; b <- mb; fab}

The problem is that in monadic computations (unlike applicative functors) values can depend on effects , and on their ordering too. So this requirement doesn't make much sense. Consider

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

This returns always True , but if you reorder the effects, it'll start returning False . Obviously by parallelizing the two computations, you'd get a very non-deterministic result.

So it's difficult to interpret the intention of bindM2 . I'd say we could divide instances of MonadParallel into two categories:

  • Those that are really deterministic and for which bindM2 is always equal to f ma mb-> do {a <- ma; b <- mb; fab} f ma mb-> do {a <- ma; b <- mb; fab} f ma mb-> do {a <- ma; b <- mb; fab} . That is, the order of effects doesn't change. Those implementations are usually defined using par , which doesn't change semantics of the program, only runs some parts in parallel.
  • Those that do depend on effect ordering, an thus bindM2 can be arbitrarily different from f ma mb-> do {a <- ma; b <- mb; fab} f ma mb-> do {a <- ma; b <- mb; fab} f ma mb-> do {a <- ma; b <- mb; fab} . It seems that currently the only such instance is IO , which uses forkIO to spawn a new thread for one of the computations.
  • So whether you accept your bindM2 as a valid instance or not depends on how you how you interpret the documentation. If you consider (2.) as invalid then your implementation is also invalid (and you should reject IO too). If you interpret (2.) as valid, then you don't have to be concerned.

    链接地址: http://www.djcxy.com/p/11664.html

    上一篇: 如何从托管的TFS迁移到

    下一篇: MonadParallel Instance for Rand