了解Scala中的随机monad

Mic*_*ael 17 random monads scala

这是我上一个问题的后续行动

Travis Brown指出这java.util.Random是副作用,并建议使用随机monad Rng 来使代码纯粹功能化.现在我正在尝试自己构建一个简化的随机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) = ...
Run Code Online (Sandbox Code Playgroud)

请注意,next返回两个新的种子和价值,而不仅仅是值.我们需要它将新种子传递给另一个函数调用.

随机点

现在让我们编写一个函数来生成单位平方的随机点.

假设我们有一个函数来生成范围[0,1]中的随机双精度

 def randomDouble(seed: Long): (Long, Double) = ... // some bit magic
Run Code Online (Sandbox Code Playgroud)

现在我们可以编写一个函数来生成一个随机点.

def randomPoint(seed: Long): (Long, (Double, Double)) = {
   val (seed1, x) = randomDouble(seed)
   val (seed2, y) = randomDouble(seed1)
   (seed2, (x, y)) 
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切都好,都randomDoublerandomPoint是纯粹的.唯一的问题是我们randomDouble构建randomPoint 临时构建.我们没有一个通用的工具来组合产生随机值的函数.

Monad 随机

现在我们将定义一个通用工具来组合产生随机值的函数.首先,我们概括了以下类型randomDouble:

type Random[A] = Long => (Long, A) // generate a random value of type A
Run Code Online (Sandbox Code Playgroud)

然后围绕它构建一个包装类.

class Random[A](run: Long => (Long, A))
Run Code Online (Sandbox Code Playgroud)

我们需要包装器来定义方法flatMap(在Haskell中作为bind)map并由for-comprehension使用.

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))})
}  
Run Code Online (Sandbox Code Playgroud)

现在我们添加一个工厂函数来创建一个简单的Random[A](顺便说一句,它绝对是确定性的而不是"随机的")这是一个返回函数(作为Haskell中的返回).

def certain[A](a: A) = new Random({seed: Long => (seed, a)})
Run Code Online (Sandbox Code Playgroud)

Random[A]是一个计算得到A型的方法的随机值flatMap,map以及功能unit提供用于构成简单的计算来构建更复杂的.例如,我们将组成两个Random[Double]来构建Random[(Double, Double)].

Monadic随机点

现在,当我们有一个单子,我们已经准备好重新审视randomPointrandomDouble.现在,我们以不同将它们定义为功能产生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))
Run Code Online (Sandbox Code Playgroud)

这个实现比前一个更好,因为它使用通用工具(flatMapcertain)来组成两个调用Random[Double]和构建Random[(Double, Double)].

现在可以重复使用此工具来构建更多生成随机值的函数.

蒙特卡罗计算Pi

现在我们可以map用来测试一个随机点是否在圆圈中:

def randomCircleTest(): Random[Boolean] = 
  randomPoint().map {case (x, y) => x * x + y * y <= 1} 
Run Code Online (Sandbox Code Playgroud)

我们还可以定义蒙特卡罗模拟 Random[A]

def monteCarlo(test: Random[Boolean], trials: Int): Random[Double] = ...
Run Code Online (Sandbox Code Playgroud)

最后是计算PI的函数

def pi(trials: Int): Random[Double] = ....
Run Code Online (Sandbox Code Playgroud)

所有这些功能都是纯粹的.只有当我们最终应用pi函数来获取pi的值时才会出现副作用.

Nam*_*ami 4

你的方法很好,虽然有点复杂。我还建议您看一下Paul ChiusanoRunar Bjarnason撰写的《Scala 函数式编程》的第 6 章。本章称为纯函数状态,它展示了如何创建纯函数随机生成器并在此基础上定义其数据类型代数以获得完整的函数组合支持。