In that post we will check out Writer of “herding cats” series by eed3si9n.
Scala with cats says:
cats.data.Writer is a monad that lets us carry a log along with a computation. We can use it to record messages, errors, or additional data about a computation, and extract the log alongside the final result.
Let’s steal idea of
Logged type from the book:
type Logged[A] = Writer[Vector[String], A]
On the left side we have our structure to keep logs.
On the right we have our
A value. Hence, we can fix the type to persist log and sole generic will be our
A type for values.
Under the hood we need
Monoid for log lines. Reasoning for having full-featured
Monoid is necessity to make logging optional and produce computation results without any logs at all.
Monoid[L].empty helps with that demand. And we need
Applicative[F] to pack values.
def liftF[F[_], L, V]( fv: F[V] )( implicit monoidL: Monoid[L], F: Applicative[F] ): WriterT[F, L, V] = WriterT(F.map(fv)(v => (monoidL.empty, v)))
Writer as WriterT parametrized with Id
Yo can notice that we started to deal with
WriterT[F, L, V] instead of
Writer is an alias for
WriterT with fixed Id data type.
type Writer[L, V] = WriterT[Id, L, V]
run that “unpack”
WriterT into tuple.
123.pure[Logged].run === (Vector(), 123)
map allows tranformation of value.
Writer("example", 2).map(_ * 2).run === ("example", 4)
You can apply lifted function to your value with
val multiplyByTwo: Writer[Vector[String], Int => Int] = Writer(Vector("multiplied by two"), _ * 2) val (logs, value) = Writer(Vector("initial state"), 2).ap(multiplyByTwo).run logs === Vector("multiplied by two", "initial state") value === 4
Writer is not something you can observe really often.
Practical example is implementations of loggers for tests.
import org.typelevel.log4cats.extras.WriterLogger val logger = WriterLogger() logger.info("Hi, I'm message") val logged: WriterT[Id, List[LogMessage], Unit] = logger.info("Hi, I'm message") val message = logged.run._1.head message.level === Info message.message === "Hi, I'm message"
Note by SystemFw (
The problem with logging in writer is that you will need WriterT of IO, which will fail to log on error