这是我之前的question
的后续内容 Travis Brown指出java.util.Random
是副作用,并建议使用随机monad Rng
library来使代码纯粹起作用。现在我正在尝试自己构建一个简化的随机monad,以了解它是如何工作的。
有意义吗?您如何修复/改进下面的解释?
首先,我们从java.util.Random
// do some bit magic to generate a new random "seed" from the given "seed"
// and return both the new "seed" and a random value based on it
def next(seed: Long, bits: Int): (Long, Int) = ...
请注意,next
会返回两者新种子和值,而不仅仅是值。我们需要它将新种子传递给另一个函数调用。
现在让我们编写一个函数来生成单位平方的随机点。
假设我们有一个函数来生成范围[0,1]
中的随机双精度 def randomDouble(seed: Long): (Long, Double) = ... // some bit magic
现在我们可以编写一个函数来生成一个随机点。
def randomPoint(seed: Long): (Long, (Double, Double)) = {
val (seed1, x) = randomDouble(seed)
val (seed2, y) = randomDouble(seed1)
(seed2, (x, y))
}
到目前为止,randomDouble
和randomPoint
都很纯粹。唯一的问题是我们撰写randomDouble
来构建randomPoint
ad hoc 。我们没有通用工具来组合产生随机值的函数。
现在我们将定义一个泛型工具来组合产生随机值的函数。首先,我们概括了randomDouble
:
type Random[A] = Long => (Long, A) // generate a random value of type A
然后围绕它构建一个包装类。
class Random[A](run: Long => (Long, A))
我们需要包装器来定义方法flatMap
(在Haskell中为 bind )和 for-comprehension 使用的map
。
class Random[A](run: Long => (Long, A)) {
def apply(seed: Long) = run(seed)
def flatMap[B](f: A => Random[B]): Random[B] =
new Random({seed: Long => val (seed1, a) = run(seed); f(a)(seed1)})
def map[B](f: A => B): Random[B] =
new Random({seed: Long = val (seed1, a) = run(seed); (seed1, f(a))})
}
现在我们添加一个 factory -function来创建一个简单的Random[A]
(绝对是确定性的,而不是&#34;随机&#34;顺便说一下)这是一个< em> return 函数(在Haskell中作为 return )。
def certain[A](a: A) = new Random({seed: Long => (seed, a)})
Random[A]
是计算,产生类型A的随机值。方法flatMap
,map
和函数unit
用于编写简单计算以构建更复杂的计算。例如,我们将撰写两个Random[Double]
来构建Random[(Double, Double)]
。
现在,当我们有一个monad时,我们已准备好重温randomPoint
和randomDouble
。现在我们将它们定义为不同的函数,产生Random[Double]
和Random[(Double, Double)]
def randomDouble(): Random[Double] = new Random({seed: Long => ... })
def randomPoint(): Random[(Double, Double)] =
randomDouble().flatMap(x => randomDouble().flatMap(y => certain(x, y))
此实现更好比前一个更好,因为它使用泛型工具(flatMap
和certain
)来组成两个{ {1}}并构建Random[Double]
。
现在可以重复使用此工具来构建更多生成随机值的函数。
现在我们可以使用Random[(Double, Double)]
来测试圈子中是否有随机点:
map
我们还可以根据def randomCircleTest(): Random[Boolean] =
randomPoint().map {case (x, y) => x * x + y * y <= 1}
Random[A]
最后是计算PI的函数
def monteCarlo(test: Random[Boolean], trials: Int): Random[Double] = ...
所有这些功能都是纯粹的。只有当我们最终应用def pi(trials: Int): Random[Double] = ....
函数来获取pi的值时才会出现副作用。
答案 0 :(得分:2)
虽然有点复杂,但你的方法相当不错。我还建议你看一下Functional Programming in Scala第6章 Paul Chiusano 和 Runar Bjarnason 。 本章称为纯函数状态,它展示了如何创建纯函数随机生成器并在其上定义其数据类型代数以获得完整的函数组合支持。