scala - 理解 Scala 中的随机 monad

标签 scala random monads

这是我之前的 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)) 
}

到目前为止,一切顺利,randomDoublerandomPoint 都是纯的。唯一的问题是我们组合 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 类型随机值的计算。方法 flatMapmap ,而函数unit用于组合简单的计算来构建更复杂的计算。例如,我们将组合两个 Random[Double] 来构建 Random[(Double, Double)]

一元随机点

现在,当我们有了一个 monad 时,我们就准备好重新访问 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))

此实现比前一个实现更好,因为它使用通用工具(flatMapcertain)组合两次 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 ChiusanoRunar Bjarnason。 本章称为“纯函数状态”,它展示了如何创建纯函数随机生成器并在此基础上定义其数据类型代数以获得完整的函数组合支持。

关于scala - 理解 Scala 中的随机 monad,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25704000/

相关文章:

class - 自类型注释阻碍了内部类的实例化。为什么?

scala - IntelliJ : Exception in thread "main" java. lang.NoClassDefFoundError: org/apache/spark/sql/types/DataType

c - 如何在C中将随机字符放入多维数组中

Haskell 从 URLType 中提取主机名

Scala:具有两个或多个选项的映射

java - Akka Actor的紧急停止

swift - 在没有重复变量的情况下生成随机计数的随机 Int 数组

php - 具有多个随机值但总和始终在一个范围内的 MySQL 查询

haskell - Haskell 中是否有任何运算符可以用 (>>) 折叠操作列表?

haskell - 重新定义 IO 以简化调试?