Writer monad

The Writer monad is a sibling of the state and the reader, oriented on modifying the state. Its main purpose is to provide a facility to write into some kind of log by passing this log between computations. The type of the log is not specified, but usually, some structure with a possibly low overhead of the append operation is chosen. To name a few suitable possibilities, you could use a Vector from the standard library or a List. In the case of the List, we need to prepend the log entries and revert the resulting log at the very end. 

Before we get too deep into the discussion about the type of log, it is good to realize that we can defer the decision until later. All we need to know is how to append an entry to the existing log. Or, in other words, how to combine two logs, one of which contains just a single entry, together. We already know about the structure with such a functionality—it is a Semigroup. Actually, we also need to be able to represent an empty log, and so our end decision will be to have a Monoid

Let's bring this together. The Writer takes two type arguments, one for the log entry, and one for the result. We also need to be able to have a Monoid for the log. The logic itself does not take anything from outside; it just returns the result and updated log:

import ch07._
final case class
Writer[W: Monoid, A](run: (A, W))

Next, we want to compose our writer with another monadic function, just like we did before:

final case class Writer[W: Monoid, A](run: (A, W)) {
def compose[B](f: A => Writer[W, B]): Writer[W, B] = Writer {
val (a, w) = run
val (b, ww) = f(a).run
val www = implicitly[Monoid[W]].op(w, ww)
(b, www)
}
}

The signature of the method is very similar to other monads we had in this chapter. Inside, we are decomposing the state of our current Writer into the result a and log w. Then, we apply the given function to the result and collect the next result and the log entries. Finally, we combine the log entries by utilizing the monoid operation and returning the result and the combined log.

We can also define the default constructor, which just returns a given argument with an empty log:

object Writer {
def apply[W: Monoid, A](a: => A): Writer[W, A] = Writer((a, implicitly[Monoid[W]].identity))
}

The monad definition is now a mechanical delegation to these methods. The only small difference is the requirement for the Monoid[W] to be available:

implicit def writerMonad[W : Monoid] = new Monad[Writer[W, ?]] {
override def unit[A](a: => A): Writer[W, A] = Writer(a)
override def flatMap[A, B](a: Writer[W, A])(f: A => Writer[W, B]): Writer[W, B] = a.compose(f)
}

Yet again, we are done, and we can start to use our new abstraction. Let's suppose that now regulations require us to write every bot movement into the journal. We are happy to comply. As long as it is only about movements, we don't need to touch the turn function—we'll only need to extend the go definition:

type WriterTracking[A] = Writer[Vector[(Double, Double)], A]

def go(speed: Float, time: Float)(boat: Boat): WriterTracking[Boat] = new WriterTracking((boat.go(speed, time), Vector(boat.position)))

We are writing the position of the boat in the journal represented by a Vector. In the definition, we merely propagate the call to the boat again and return the position of the boat before the move as the log entry. We also need to satisfy the monoid requirement. The monoid is defined in a similar fashion to the one we had in Chapter 7, Understanding Algebraic Structures:

implicit def vectorMonoid[A]: Monoid[Vector[A]] = 
new Monoid[Vector[A]] {
override def identity: Vector[A] = Vector.empty[A]
override def op(l: Vector[A], r: Vector[A]): Vector[A] = l ++ r
}

With these preparations, we are ready to move our boat once again in SBT session using the run command:

import Monad.writerMonad
import Boat.{move, boat, turn}
println(move(go, turn[WriterTracking])(Writer(boat)).run)

(Boat(0.4,(401.95408575015193,192.15963378398988)),Vector((0.0,0.0), (50.0,0.0), (401.0330247561491,191.77021544168122)))

We are passing the augmented go function and the original turn function (though typed with the WriterTracking) as a first parameter list and a boat wrapped in the Writer as a second parameter list. The output speaks for itselfit is the original result and vector containing positions of our boat before each moveall without touching the definition of the steering logic!

The Writer monad concludes our tour of the land of monads. In the next chapter, we'll take a look at combining them. If your intuition tells you that it can be a bit more involving than combining applicatives—after all, there is a whole chapter dedicated to that topic—then you're right. It is more complex, but also more interesting. Let's take a look!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.49.247