`Writer`

is a `WriterT`

of `ID`

:

`type Writer[W, A] = WriterT[ID, W, A]`

`WriterT`

of `F`

wraps a pair of some value
`A`

and some "writeable" `W`

:

`case class WriterT[F[_], W, A](run: F[(A, W)])`

The monad for `WriterT`

requires that `F`

has a
monad, and `W`

has a semigroup:

```
implicit def writerTMonad[F[_]: Monad, W: Monoid]: Monad[[A] =>> WriterT[F,W,A]] =
new Monad[[A] =>> WriterT[F,W,A]]:
override def pure[A](x: A): WriterT[F, W, A] =
WriterT(implicitly[Monad[F]].pure((x, implicitly[Monoid[W]].mempty)))
override def flatMap[A, B](ma: WriterT[F, W, A])(f: A => WriterT[F, W, B]): WriterT[F, W, B] =
import Monad.MonadOps
import Semigroup.SemigroupOps
WriterT(
.run.flatMap { case (a, w1) =>
maf(a).run.map { case (b, w2) =>
(b, w1 <> w2)
}
}
)
```

Printing to standard output might be one of the earliest debugging
techniques we discover. We write a program, sprinkle some
`println`

statements here and there, and get to see the
program's runtime state. I still occasionally find myself reaching for
this technique to get basic runtime inspection of a program. Sometimes
this takes the form of a logging framework, but often it's just plain
old printing to the screen.

It can be hard to correlate log messages with program state, and even harder to manage consistency in logging behavior across development, testing, and production environments. Fortunately, the writer monad offers a way out of this bind - we can have our logging and maintain referential transparency too!

Let's start with a side-effectey example. Consider the following arithmetic functions:

```
val add: Int => Int => Int =
=> y => x + y
x
val mult: Int => Int => Int =
=> y => x * y
x
val div: Int => Int => Option[Double] =
=> y => if (y == 0) None else Some(x.toDouble / y)
x
val parse: String => Option[Int] =
=> try { Some(x.toInt) } catch { case t: Throwable => None } x
```

The `add`

and `mult`

functions both take two
integers and produce another integer. The `div`

and
`parse`

functions, which can fail (since they're only
partially defined over their domains), both produce an
`Option`

of a number.

We can string these functions together, taking advantage of
`Option`

's `map`

and `flatMap`

functions, to perform a compound operation:

```
for
<- parse("42")
x1 = mult(x1)(2)
x2 = add(x2)(42)
x3 <- div(x3)(3)
x4 yield x4 // Some(42.0)
```

We can also interleave some debugging statements using
`println`

:

```
for
<- parse("42")
x1 = println("x1: " + x1) // prints "x1: 42"
_ = mult(x1)(2)
x2 = println("x2: " + x2) // prints "x2: 84"
_ = add(x2)(42)
x3 = println("x3: " + x3) // prints "x3: 126"
_ <- div(x3)(3)
x4 = println("x4: " + x4) // prints "x4: 42.0"
_ yield x4 // Some(42.0)
```

But we want to avoid both the side-effect of printing, and the dissociation of the result data from the log messages.

Let's build a special logging data structure that, when composed with
`Option`

instances, allows us to keep the same shape of our
for comprehension, but return the log (along with the final result) as a
sequence of log messages:

```
case class LogOption[A](val run: Option[(A, Seq[String])])
implicit val logOptionMonad: Monad[LogOption] =
new Monad[LogOption]:
override def pure[A](x: A): LogOption[A] =
LogOption(Some(x, Nil))
override def flatMap[A, B](
: LogOption[A]
ma)(f: A => LogOption[B]): LogOption[B] =
import Monad.MonadOps
LogOption(
.run.flatMap { case (a, l) =>
maf(a).run.map { case (b, l2) =>
(b, l ++ l2)
}
}
)
def logOption[A](x: Option[A]): LogOption[A] =
LogOption(x.map(a => (a, Nil)))
def logOption(x: String): LogOption[Unit] =
LogOption(Some(((), Seq(x))))
```

The `LogOption`

class wraps an optional pair of a result
plus a log. This could be as simple as:

`Some((42.0, List("returning 42.0")))`

The `LogOption`

class specifies how (via `map`

)
to apply a function to its result type, as well as how (via
`flatMap`

) to compose itself with a function thet returns
another `LogOption`

.

We can use it with only minor modification to the code above:

```
val x5 =
import Monad.MonadOps
for
<- logOption(parse("42"))
x1 <- logOption("x1: " + x1)
_ = mult(x1)(2)
x2 <- logOption("x2: " + x2)
_ = add(x2)(42)
x3 <- logOption("x3: " + x3)
_ <- logOption(div(x3)(3))
x4 <- logOption("x4: " + x4)
_ yield x4
println(s"x5: ${x5.run}") // prints "x5: Some((42.0,List(x1: 42, x2: 84, x3: 126, x4: 42.0)))"
```

Now we have a way to compose functions that return raw integers and
`Option`

s of integers, while building up a queue of log
messages. Nothing is written to standard output, no external state is
altered, and in fact the code isn't even executed until it is initiated
with an empty log via `x.run(Nil)`

.

It turns out that `LogOption`

is a specialization of the
writer monad transformer:

```
type LogT[F[_], A] = WriterT[F, Seq[String], A]
def logT[F[_]: Monad, A](x: F[A]): LogT[F, A] =
import Monad.MonadOps
new LogT(x.map(a => (a, Nil)))
def log(x: Option[String]): LogT[Option, Unit] =
new LogT(Some(((), x.toSeq)))
def log(x: String): LogT[Option, Unit] =
new LogT(Some(((), Seq(x))))
```

```
val x6 =
import Monad.MonadOps
for
<- logOption(parse("42"))
x1 <- logOption("x1: " + x1)
_ = mult(x1)(2)
x2 <- logOption("x2: " + x2)
_ = add(x2)(42)
x3 <- logOption("x3: " + x3)
_ <- logOption(div(x3)(3))
x4 <- logOption("x4: " + x4)
_ yield x4
println(s"x6: ${x6.run}") // prints "x6: Some((42.0,List(x1: 42, x2: 84, x3: 126, x4: 42.0)))"
```

This file is literate Scala, and can be run using Codedown:

```
$ curl \
https://earldouglas.com/posts/type-classes/applicative.md \
https://earldouglas.com/posts/type-classes/functor.md \
https://earldouglas.com/posts/type-classes/id.md \
https://earldouglas.com/posts/type-classes/monad.md \
https://earldouglas.com/posts/type-classes/monoid.md \
https://earldouglas.com/posts/type-classes/semigroup.md \
https://earldouglas.com/posts/type-classes/writer.md |
codedown scala |
scala-cli -q --scala 3.1.3 _.sc
x1: 42
x2: 84
x3: 126
x4: 42.0
x5: Some((42.0,List(x1: 42, x2: 84, x3: 126, x4: 42.0)))
x6: Some((42.0,List(x1: 42, x2: 84, x3: 126, x4: 42.0)))
```