Tuesday, 2 January 2018

Writer Monad from Scala Cats

Once upon a time there was some stinky side affecting code with println all over the place.


object Cinderella1 extends App {
def f(): Int = {
println("Generating random int")
42
}
def g(i: Int): Int = {
println("Applying complex mathematical formula")
i * 2
}
g(f())
}
How horrible! No, Cinderella, you definitely can't meet the prince of MonadLand looking like that!
Fortunately her Functional Fairy Godmother is on hand.



 'Ah ha' she cries! From each function you need to return both the int (which is the existing return type) and some Strings to represent the logs!

'Easy peasy' she says and with a swish of her magic wand she transforms Cinderella.

object Cinderella2 extends App {
def f: (Int, Vector[String]) = {
(42, Vector("Generating random int"))
}
def g(i: Int): (Int, Vector[String]) = {
(i % 2, Vector("Applying complex mathematical formula"))
}
val (i, vs1) = f
val (result, vs2) = g(i)
val printStatements = vs1 ++ vs2
println(printStatements)
println(result)
}
Well I guess this is ok, but look how much more effort Cinderella has to go to to compose f and g! That's not very elegant at all!

'Please, Fairy Godmother', Cinderella begs, 'Isn't there anything more you can do?'

Now it's a little-known Disney secret that Cinderella's Functional Fairy Godmother is actually an enormous fan of Cats and has a clever Writer monad trick up her sleeve.

object Cinderella3 extends App {
import cats.data.Writer
import cats.instances.vector._
import cats.syntax.applicative._
import cats.syntax.writer._
type Logged[A] = Writer[Vector[String], A]
def f: Logged[Int] = for {
n <- 42.pure[Logged]
_ <- Vector("Generating random int").tell
} yield n
def g(i: Int): Logged[Int] = for {
n <- i.pure[Logged]
_ <- Vector("Applying complex mathematical formula").tell
} yield n * 2
private val result1 = for {
i <- f
result <- g(i)
} yield result
private val result2 = f.flatMap(g)
println(result1)
println(result2)
}
Output:

WriterT((Vector(Generating random int, Applying complex mathematical formula),84))
WriterT((Vector(Generating random int, Applying complex mathematical formula),84))

'Oh thank you Functional Fairy Godmother', cries Cinderella, delighted.

FFG winks and whispers, 'I just love a good for comprehension! There are actually a lot of different ways of creating a writer!', and she slips some example code into Cinderella's pocket for later bedtime reading, in case things with the prince don't go to plan:

object DifferentWaysOfCreatingACatsWriter extends App {
import cats.data.Writer
import cats.instances.vector._
import cats.syntax.applicative._
import cats.syntax.writer._
type Logged[A] = Writer[Vector[String], A]
def f1: Logged[Int] = {
val magicNumber = 42
magicNumber.writer(Vector("Generating random int"))
}
def f2: Logged[Int] = for {
n <- 42.pure[Logged]
_ <- Vector("Generating random int").tell
} yield n
def f3: Logged[Int] = {
val magicNumber = (42
Writer(Vector("Generating random int"), magicNumber)
}
println(f1)
println(f2)
println(f3)
}
Cinderella was so intrigued by these examples that she stayed up all night reading Dave Gurnell and Noel Walsh's book Scala with Cats and skipped the ball entirely.


And she lived happily ever after. 

No comments:

Post a Comment

Scala with Cats: Answers to revision questions

I'm studying the 'Scala with Cats' book. I want the information to stick so I am applying a technique from 'Ultralearning...