MonadParallel Instance for Rand
我目前正在ReaderT r (Rand StdGen) a
,我希望并行运行它。 我遇到过Monad Parallel,看起来它会做我想要的。
对于ReaderT
已经有MonadParallel的实例,但我必须从monad-random为Rand
创建我自己的实例。 但是,我不确定我是否做得对。 我并不太熟悉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
虽然我觉得有这种情况可以忽略这个(数字仍然是随机的,对吧?)我也不禁感到我失去了一些东西。 这是好的还是有更好的解决方案?
我主要担心的是这违反了以下保证:
除了副作用的可能顺序之外,这个函数相当于f ma mb-> do {a <- ma; b <- mb; fab}
f ma mb-> do {a <- ma; b <- mb; fab}
这些会有不同的结果:
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
这确实引发了关于split
,伪随机数和关于纯度的随机数性质的有趣问题。 我很难想象一个你的实例会产生不利影响的情况,但是软件系统的复杂性从来没有让我感到惊讶。 正因为bindM2
,即使它是随机数字,我也不会违反对bindM2
的期望。
MonadParallel
的bindM2
有一个固有的问题。 其文档说:
并行执行两个monadic计算; 当他们都完成后,将结果传递给函数。 除了副作用的可能顺序之外,这个函数相当于f ma mb-> do {a <- ma; b <- mb; fab}
f ma mb-> do {a <- ma; b <- mb; fab}
问题在于,在一元计算中(与应用函子不同), 值可能取决于效果 , 也取决于它们的排序。 所以这个要求没有多大意义。 考虑
do
a <- getCurrentTime -- from Date.Time
b <- getCurrentTime
return (a <= b)
这将始终返回True
,但如果您重新排列效果,它将开始返回False
。 显然通过并行化两个计算,你会得到一个非确定性的结果。
所以很难解释bindM2
的意图。 我会说我们可以将MonadParallel
实例分成两类:
bindM2
总是等于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}
。 也就是说,效果的顺序不会改变。 这些实现通常使用par
定义,不会更改程序的语义,只能并行运行某些部分。 bindM2
,因此结合bindM2
可以任意地不同于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}
。 看来目前唯一的这种实例是IO
,它使用forkIO
为其中一个计算生成一个新线程。 因此,无论您是否接受bindM2
作为有效实例,都取决于您如何解释文档。 如果你认为(2.)是无效的,那么你的实现也是无效的(你也应该拒绝IO
)。 如果你认为(2.)是有效的,那么你就不必担心。