Translate

sábado, 2 de julio de 2022

Mónadas en Cats parte 14

cats.Eval es una mónada que nos permite abstraernos sobre diferentes modelos de evaluación. Por lo general, hablamos de dos modelos de este tipo: eager y lazy, también llamados llamada por valor y llamada por nombre, respectivamente. Eval también permite memorizar un resultado, lo que nos brinda una evaluación de llamada por necesidad. Eval también es seguro para la pila, lo que significa que podemos usarlo en recursiones muy profundas sin explotar la pila.

¿Qué significan estos términos para modelos de evaluación? Veamos algunos ejemplos. Veamos primero Scala vals. Podemos ver el modelo de evaluación utilizando un cálculo con un efecto secundario visible. En el siguiente ejemplo, el código para calcular el valor de x ocurre en el lugar donde se define en lugar de acceder. Acceder a x recupera el valor almacenado sin volver a ejecutar el código.

val x = {

    println("Computing X")

    math.random

}

// Computing X

// x: Double = 0.0396922778013471

x // first access

// res0: Double = 0.0396922778013471

x // second access

// res1: Double = 0.0396922778013471


Este es un ejemplo de evaluación de llamada por valor:

• el cómputo se evalúa en el punto donde se define (eager); y

• el cálculo se evalúa una vez (se memoriza).

Veamos un ejemplo usando un def. El código para calcular y a continuación no se ejecuta hasta que lo usamos y se vuelve a ejecutar en cada acceso:


def y = {

    println("Computing Y")

    math.random

}

y // first access

// Computing Y

// res2: Double = 0.5270290953284378 

y // second access

// Computing Y

// res3: Double = 0.348549829974959


Estas son las propiedades de la evaluación llamada por nombre:

• el cálculo se evalúa en el punto de uso (lazy); y

• el cálculo se evalúa cada vez que se utiliza (no se memoriza).

Por último, pero no menos importante, los valores perezosos son un ejemplo de evaluación de llamada por necesidad. El código para calcular z a continuación no se ejecuta hasta que lo usamos por primera vez (perezoso). Luego, el resultado se almacena en caché y se reutiliza en accesos posteriores (memorizados):


lazy val z = {

    println("Computing Z")

    math.random

}

z // first access

// Computing Z

// res4: Double = 0.6672110951657263 // first access

z // second access

// res5: Double = 0.6672110951657263


Resumamos. Hay dos propiedades de interés:

• evaluación en el punto de definición (ansioso) versus en el punto de uso (perezoso); y

• los valores se guardan una vez evaluados (memorizados) o no (no memorizados).

Hay tres posibles combinaciones de estas propiedades:

• llamada por valor que está ansiosa y memorizada;

• llamada por nombre que es perezosa y no memorizada; y

• llamar por necesidad, que es perezoso y memorizado.

La combinación final, ansiosa y no memorizada, no es posible.


Eval tiene tres subtipos: Now, Always, y Later. Corresponden a llamada por valor, llamada por nombre y llamada por necesidad, respectivamente. Los construimos con tres métodos constructores, que crean instancias de las tres clases y las devuelven escritas como Eval:

import cats.Eval

val now = Eval.now(math.random + 1000)

// now: Eval[Double] = Now(1000.7009661848473)

val always = Eval.always(math.random + 3000)

// always: Eval[Double] = cats.Always@2a4e7955

val later = Eval.later(math.random + 2000)

// later: Eval[Double] = cats.Later@7684da18


Podemos extraer el resultado de un Eval usando su método de valor:


now.value

// res6: Double = 1000.7009661848473

always.value

// res7: Double = 3000.5158510235524

later.value

// res8: Double = 2000.6995448328964


Cada tipo de Eval calcula su resultado utilizando uno de los modelos de evaluación definidos anteriormente. Eval.now captura un valor ahora mismo. Su semántica es similar a un val: ansioso y memorizado:

val x = Eval.now{

println("Computing X")

math.random

}

// Computing X

// x: Eval[Double] = Now(0.6969571260771719)

x.value // first access

// res10: Double = 0.6969571260771719 // first access

x.value // second access

// res11: Double = 0.6969571260771719


Eval.always captura un cálculo perezoso, similar a una definición:


val y = Eval.always{

println("Computing Y")

math.random

}

// y: Eval[Double] = cats.Always@6d355284

y.value // first access

// Computing Y

// res12: Double = 0.8575236846076497 // first access

y.value // second access

// Computing Y

// res13: Double = 0.15716382484701563


Finalmente, Eval.later captura un cálculo memorizado perezoso, similar a un valor perezoso:


val z = Eval.later{

println("Computing Z")

math.random

}

// z: Eval[Double] = cats.Later@3429dabc

z.value // first access

// Computing Z

// res14: Double = 0.5149108999064906 // first access

z.value // second access

// res15: Double = 0.5149108999064906