这是我之前的 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] 范围内的随机 double 值
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。我们没有通用工具来组合产生随机值的函数。
Monad 随机
现在我们将定义一个通用工具来组合产生随机值的函数。首先,我们概括一下randomDouble
的类型:
type Random[A] = Long => (Long, A) // generate a random value of type A
然后围绕它构建一个包装类。
class Random[A](run: Long => (Long, A))
我们需要包装器来定义 flatMap
方法(如 Haskell 中的bind)和用于理解的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))})
}
现在我们添加一个工厂函数来创建一个简单的Random[A]
(顺便说一下,这绝对是确定性的,而不是“随机的”)这是一个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
)组合两次 Random[Double]
调用并构建 Random[(Double, Double)]
。
现在可以重新使用此工具来构建更多生成随机值的函数。
Pi 的蒙特卡罗计算
现在我们可以使用map
来测试随机点是否在圆内:
def randomCircleTest(): Random[Boolean] =
randomPoint().map {case (x, y) => x * x + y * y <= 1}
我们还可以根据Random[A]
定义蒙特卡罗模拟
def monteCarlo(test: Random[Boolean], trials: Int): Random[Double] = ...
最后是计算 PI 的函数
def pi(trials: Int): Random[Double] = ....
所有这些函数都是纯函数。仅当我们最终应用 pi
函数来获取 pi 的值时,才会出现副作用。
最佳答案
你的方法很好,虽然有点复杂。我还建议你看看Functional Programming in Scala的第6章作者:Paul Chiusano 和 Runar Bjarnason。 本章称为“纯函数状态”,它展示了如何创建纯函数随机生成器并在此基础上定义其数据类型代数以获得完整的函数组合支持。
关于scala - 理解 Scala 中的随机 monad,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25704000/