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:
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. 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.
上一篇: 如何从托管的TFS迁移到