I already wrote a post about reader monad and It’s time to write about Writer Monad. First, I thought that Writer Monad is opposite of Reader Monad. But it’s quite different from Reader Monad. Let’s say that Writer Monad is…
Writer Monad is stack something while operating.
Hm… I think it’s not a very well summarized sentence. If any reader has nicer summarized one, comment me. Below example is from mastering advanced Scala.
Example
type Writer id defined in cats like this.
type Writer[L, V] = WriterT[Id, L, V]
As you can see, Writer Monad accept type L & V. type L is a type which stacks something. and V is a result(return) value. Id position in WirterT is something wraps the result. Here is Id so, never mind.
You can simply use Writer Monad like.
import cats.data.Writer def greetW(name: String, logged: Boolean): Writer[List[String], String] = Writer(List("Compose a greeting"), { val userName = if(logged) name else "User" s"Hello $userName" }) def isLoggedW(name: String): Writer[List[String], Boolean] = Writer(List("Checking if user is logged in"), name.length == 3) import cats.instances.list._ val name = "Joe" val resultW: Writer[List[String], String] = for { logged <- isLoggedW(name) greeting <- greetW(name, logged) } yield greeting val (log, result) = resultW.run // log: List[String] = List(Checking if user is logged in, Compose a greeting) //result: String = Hello Joe
Yes. It’s very simple. It just stacks some strings in L(List[String]) and returns value in V(String). I think it’s very useful.
How is it works?
Of course, you can have curious that how’s is it works? As you can see in ‘for comprehension’, there is nowhere to pass List[String]. But after finishing for comprehension, it splits out log: List[String] and result: String.
Let’s remind this.
For comprehension is syntactic sugar of flatMap.
You can also write like this.
val flatMapResult: Writer[List[String], String] = isLoggedW(name).flatMap(logged => greetW(name, logged))
It’s the same result of for comprehension statements.
Ok. It’s flatMap. Then, Let’s deep dive to WriterT.flatMap
final case class WriterT[F[_], L, V](run: F[(L, V)]) { ... def flatMap[U](f: V => WriterT[F, L, U])(implicit flatMapF: FlatMap[F], semigroupL: Semigroup[L]): WriterT[F, L, U] = WriterT { flatMapF.flatMap(run) { lv => flatMapF.map(f(lv._2).run) { lv2 => (semigroupL.combine(lv._1, lv2._1), lv2._2) } } } ... }
In this code, never mind another but line:7. In Line 7, they combine two List[String]. Stacking is occurred in here. WriterT is different from others. value run’s type is F[(L, V)] and it does flatMap with it. So we can stack List[String]
WriterT Example
WriterT is Monad Transformer of Writer. So, you can use like this.
import cats.data.WriterT import cats.instances.future._ import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global def greetWFuture(name: String, logged: Boolean): WriterT[Future, List[String], String] = WriterT(Future(List("Compose a greeting"), { val userName = if(logged) name else "User" s"Hello $userName" })) def isLoggedWFuture(name: String): WriterT[Future, List[String], Boolean] = WriterT(Future(List("Checking if user is logged in"), name.length == 3)) val resultWFuture: WriterT[Future, List[String], String] = for { logged <- isLoggedWFuture(name) greeting <- greetWFuture(name, logged) } yield greeting val futureResult: Future[(List[String], String)] = resultWFuture.run import scala.concurrent.Await import scala.concurrent.duration._ val (logAwait, resultAwait) = Await.result(futureResult, 2 second)
It’s very simple. We can use Writer Monad inside another Monad.
import cats.data.Writer | |
def greetW(name: String, logged: Boolean): Writer[List[String], String] = | |
Writer(List("Compose a greeting"), { | |
val userName = if(logged) name else "User" | |
s"Hello $userName" | |
}) | |
def isLoggedW(name: String): Writer[List[String], Boolean] = | |
Writer(List("Checking if user is logged in"), name.length == 3) | |
import cats.instances.list._ | |
val name = "Joe" | |
val resultW: Writer[List[String], String] = for { | |
logged <- isLoggedW(name) | |
greeting <- greetW(name, logged) | |
} yield greeting | |
val (log, result) = resultW.run | |
val flatMapResult: Writer[List[String], String] = isLoggedW(name).flatMap(logged => greetW(name, logged)) | |
import cats.data.WriterT | |
import cats.instances.future._ | |
import scala.concurrent.Future | |
import scala.concurrent.ExecutionContext.Implicits.global | |
def greetWFuture(name: String, logged: Boolean): WriterT[Future, List[String], String] = | |
WriterT(Future(List("Compose a greeting"), { | |
val userName = if(logged) name else "User" | |
s"Hello $userName" | |
})) | |
def isLoggedWFuture(name: String): WriterT[Future, List[String], Boolean] = | |
WriterT(Future(List("Checking if user is logged in"), name.length == 3)) | |
val resultWFuture: WriterT[Future, List[String], String] = for { | |
logged <- isLoggedWFuture(name) | |
greeting <- greetWFuture(name, logged) | |
} yield greeting | |
val futureResult: Future[(List[String], String)] = resultWFuture.run | |
import scala.concurrent.Await | |
import scala.concurrent.duration._ | |
val (logAwait, resultAwait) = Await.result(futureResult, 2 second) |