Comprender la mónada aleatoria en Scala

Este es un seguimiento de mi anteriorpregunta

Travis Brown señaló esojava.util.Random tiene efectos secundarios y sugirió una mónada al azarRng biblioteca para hacer el código puramente funcional. Ahora estoy tratando de construir una mónada aleatoria simplificada por mí mismo para comprender cómo funciona.

Tiene sentido ? ¿Cómo arreglarías / mejorarías la explicación a continuación?

Generador aleatorio

Primero, plagiamos una función generadora aleatoria dejava.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) = ...

Tenga en cuenta quenext devolucionesambos la nueva semilla y el valor en lugar de solo el valor. Lo necesitamos para pasar la nueva semilla a otra invocación de función.

Punto aleatorio

Ahora escribamos una función para generar un punto aleatorio en un cuadrado unitario.

Supongamos que tenemos una función para generar un doble aleatorio en el rango [0, 1]

 def randomDouble(seed: Long): (Long, Double) = ... // some bit magic

Ahora podemos escribir una función para generar un punto aleatorio.

def randomPoint(seed: Long): (Long, (Double, Double)) = {
   val (seed1, x) = randomDouble(seed)
   val (seed2, y) = randomDouble(seed1)
   (seed2, (x, y)) 
}

Hasta ahora, todo bien y ambosrandomDouble yrandomPoint son puros El único problema es que componimosrandomDouble para construirrandomPoint ad hoc. No tenemos ungenérico herramienta para componer funciones que producen valores aleatorios.

MonadaAleatorio

Ahora definiremos ungenérico herramienta para componer funciones que producen valores aleatorios. Primero, generalizamos el tipo derandomDouble:

type Random[A] = Long => (Long, A) // generate a random value of type A

y luego construir una clase de envoltura alrededor de él.

class Random[A](run: Long => (Long, A))

Necesitamos el contenedor para definir los métodos.flatMap (comoenlazar en Haskell) ymap usado porpara la comprensión.

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))})
}  

Ahora agregamos unfábrica-función para crear un trivialRandom[A] (que es absolutamente determinista en lugar de "aleatorio", por cierto) Esta es unaregreso funcionan comoregreso en Haskell)

def certain[A](a: A) = new Random({seed: Long => (seed, a)})

Random[A] es uncálculo produciendo un valor aleatorio de tipo A. Los métodosflatMap , mapy funciónunit sirve paracomposición cálculos simples para construir los más complejos. Por ejemplo, compondremos dosRandom[Double] para construirRandom[(Double, Double)].

Punto aleatorio monádico

Ahora cuando tenemos una mónada estamos listos para volver a visitarrandomPoint yrandomDouble. Ahora los definimos de manera diferente como funciones que producenRandom[Double] yRandom[(Double, Double)]

def randomDouble(): Random[Double] = new Random({seed: Long => ... })
def randomPoint(): Random[(Double, Double)] =
  randomDouble().flatMap(x => randomDouble().flatMap(y => certain(x, y))

Esta implementación esmejor que el anterior ya que utiliza ungenérico herramienta (flatMap ycertain) para componer dos llamadas deRandom[Double] y construirRandom[(Double, Double)].

Ahora puede reutilizar esta herramienta para construir más funciones que generen valores aleatorios.

Cálculo de Montecarlo de Pi

Ahora podemos usarmap para probar si hay un punto aleatorio en el círculo:

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

También podemos definir una simulación de Montecarlo en términos deRandom[A]

def monteCarlo(test: Random[Boolean], trials: Int): Random[Double] = ...

y finalmente la función para calcular PI

def pi(trials: Int): Random[Double] = ....

Todas esas funciones son puras. Los efectos secundarios ocurren solo cuando finalmente aplicamospi función para obtener el valor de pi.

Respuestas a la pregunta(1)

Su respuesta a la pregunta