mónada monadologia monadas leibniz las filosofía filosofia educatina doctrina scala random monads

scala - monadologia - monadas filosofia



Comprender la mónada aleatoria en Scala (1)

Esto es una continuación de mi pregunta anterior

Travis Brown señaló que java.util.Random tiene java.util.Random colaterales y sugirió una biblioteca Rng mónada aleatoria para hacer que el código sea puramente funcional. Ahora estoy tratando de construir una mónada aleatoria simplificada por mi cuenta para entender cómo funciona.

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

Generador aleatorio

Primero plagiamos una función de generación aleatoria de 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) = ...

Tenga en cuenta que a next devuelve tanto el nuevo valor inicial como el valor en lugar de solo el valor. Necesitamos 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, tan bueno y tanto randomDouble como randomPoint son puros. El único problema es que randomDouble para crear randomPoint ad hoc . No tenemos una herramienta genérica para componer funciones que produzcan valores aleatorios.

Monad Aleatorio

Ahora definiremos una herramienta genérica para componer funciones que den valores aleatorios. Primero, generalizamos el tipo de randomDouble :

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

y luego construye una clase de contenedor alrededor de él.

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

Necesitamos el contenedor para definir los métodos flatMap (como se une en Haskell) y el map utilizado por for-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 una función de fábrica para crear un Random[A] trivial (que es absolutamente determinista en lugar de "aleatorio", por cierto) Esta es una función de retorno (como retorno en Haskell).

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

Random[A] es un cálculo que arroja un valor aleatorio de tipo A. Los métodos flatMap , map y function unit sirven para componer cálculos simples para construir otros más complejos. Por ejemplo, compondremos dos Random[Double] para construir Random[(Double, Double)] .

Monadic Random Point

Ahora cuando tenemos una mónada, estamos listos para volver a visitar randomPoint y randomDouble . Ahora los definimos de forma diferente como funciones que producen Random[Double] y 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))

Esta implementación es mejor que la anterior ya que utiliza una herramienta genérica ( flatMap y certain ) para componer dos llamadas de Random[Double] y build Random[(Double, Double)] .

Ahora puede volver a usar esta herramienta para crear más funciones que generen valores aleatorios.

Cálculo Monte-Carlo de Pi

Ahora podemos usar el map 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 Monte-Carlo en términos de Random[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 aplicamos la función pi para obtener el valor de pi.


Su enfoque es bastante bueno, aunque es un poco complicado. También sugerí que echaran un vistazo al Capítulo 6 de la Programación Funcional en Scala por Paul Chiusano y Runar Bjarnason . El capítulo se llama Estado puramente funcional y muestra cómo crear un generador aleatorio puramente funcional y definir su álgebra de tipo de datos además de eso para tener soporte completo de composición de funciones.