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