Translate

martes, 12 de julio de 2022

Mónadas en Cats parte 17

cats.data.Writer es una mónada que nos permite llevar un registro junto con un cálculo. Podemos usarlo para registrar mensajes, errores o datos adicionales sobre un cálculo y extraer el registro junto con el resultado final.

Un uso común para Writers es registrar secuencias de pasos en cálculos de subprocesos múltiples donde las técnicas de registro imperativas estándar pueden generar mensajes intercalados de diferentes contextos. Con Writer, el registro para el cálculo está vinculado al resultado, por lo que podemos ejecutar cálculos simultáneos sin mezclar registros.

Un Writer[W, A] tiene dos valores: un registro de tipo W y un resultado de tipo A. Podemos crear un Writer a partir de valores de cada tipo de la siguiente manera:


import cats.data.Writer

import cats.instances.vector._ // for Monoid

Writer(Vector("It was the best of times","it was the worst of times"), 1859)

// res0: cats.data.WriterT[cats.package.Id, Vector[String], Int] = WriterT(

// (Vector("It was the best of times", "it was the worst of times"), 1859)

// )


Tenga en cuenta que el tipo informado en la consola es en realidad WriterT[Id, Vector[String], Int] en lugar de Writer[Vector[String], Int] como cabría esperar. En el espíritu de la reutilización de código, Cats implementa Writer en términos de otro tipo, WriterT. WriterT es un ejemplo de un nuevo concepto llamado transformador de mónadas, que trataremos más adelante

Tratemos de ignorar este detalle por ahora. Writer es un alias de tipo para WriterT, por lo que podemos leer tipos como WriterT[Id, W, A] como Writer[W, A]:

type Writer[W, A] = WriterT[Id, W, A]

Para mayor comodidad, Cats proporciona una forma de crear Writers especificando solo el registro o el resultado. Si solo tenemos un resultado, podemos usar la sintaxis pure estándar. Para hacer esto, debemos tener un Monoid[W] en el alcance para que Cats sepa cómo producir un registro vacío:

import cats.instances.vector._ // for Monoid

import cats.syntax.applicative._ // for pure

type Logged[A] = Writer[Vector[String], A]

123.pure[Logged]

// res1: Logged[Int] = WriterT((Vector(), 123))

Si tenemos un registro y ningún resultado, podemos crear un Writer[Unit] usando la sintaxis tell de cats.syntax.writer:

import cats.syntax.writer._ // for tell

Vector("msg1", "msg2", "msg3").tell

// res2: Writer[Vector[String], Unit] = WriterT(

// (Vector("msg1", "msg2", "msg3"), ())

// )

Si tenemos un resultado y un registro, podemos usar Writer.apply o podemos usar la sintaxis de Writer de cats.syntax.writer:

import cats.syntax.writer._ // for writer

val a = Writer(Vector("msg1", "msg2", "msg3"), 123)

// a: cats.data.WriterT[cats.package.Id, Vector[String], Int] = WriterT(

// (Vector("msg1", "msg2", "msg3"), 123)

// )

val b = 123.writer(Vector("msg1", "msg2", "msg3"))

// b: Writer[Vector[String], Int] = WriterT(

// (Vector("msg1", "msg2", "msg3"), 123)

// )

Podemos extraer el resultado y el registro de un Writer utilizando los métodos de valor y escrito respectivamente:

val aResult: Int = a.value

// aResult: Int = 123

val aLog: Vector[String] =a.written

// aLog: Vector[String] = Vector("msg1", "msg2", "msg3")

Podemos extraer ambos valores al mismo tiempo usando el método de ejecución:

val (log, result) = b.run

// log: Vector[String] = Vector("msg1", "msg2", "msg3")

// result: Int = 123