了解Scala中的随机monad

时间:2014-09-06 19:31:54

标签: 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]

中的随机双精度
 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 )和 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的随机值。方法flatMapmap和函数unit用于编写简单计算以构建更复杂的计算。例如,我们将撰写两个Random[Double]来构建Random[(Double, Double)]

Monadic Random Point

现在,当我们有一个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)来组成两个{ {1}}并构建Random[Double]

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

Pi

的Monte-Carlo计算

现在我们可以使用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的值时才会出现副作用。

1 个答案:

答案 0 :(得分:2)

虽然有点复杂,但你的方法相当不错。我还建议你看一下Functional Programming in Scala第6章 Paul Chiusano Runar Bjarnason 。 本章称为纯函数状态,它展示了如何创建纯函数随机生成器并在其上定义其数据类型代数以获得完整的函数组合支持。