了解Scala中的随机单子
这是我之前
question的后续行动
Travis Brown指出,java.util.Random是副作用的,并建议一个随机的monad Rng library使代码纯功能。现在我试图用自己的方式构建一个简化的随机单子,以了解它的工作原理。 是否有意义 ?你将如何修正/改进下面的解释? 随机发生器 首先我们剽窃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) = ... 请注意,接下来将返回新的种子和值,而不仅仅是值。我们需要它将新的种子传递给另一个函数调用。 随机点 现在我们来写一个函数来生成单位平方的随机点。 假设我们有一个函数来生成范围[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特设。我们没有一个通用的工具来组合产生随机值的函数。 莫纳德随机 现在我们将定义一个通用工具来组合产生随机值的函数。首先,我们推断randomDouble的类型: type Random[A] = Long => (Long,A) // generate a random value of type A 然后在其周围构建一个包装类。 class Random[A](run: Long => (Long,A)) 我们需要包装器来定义方法flatMap(在Haskell中绑定)和由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))}) } 现在我们添加一个工厂函数来创建一个简单的Random [A](这绝对是确定性的,而不是“随机”),这是一个返回函数(作为Haskell中的返回)。 def certain[A](a: A) = new Random({seed: Long => (seed,a)}) 随机[A]是产生类型A的随机值的计算。平面图,地图和功能单元的方法用于构建简单的计算以构建更复杂的。例如,我们将组成两个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。 该章节称为纯功能状态,它显示了如何创建纯函数的随机生成器并定义其数据类型代数,以具有完整的功能组合支持。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |