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.