Journey into the IO Monad (Part 2)

Raymond Tay
Oct 14 · 4 min read
Peanut cake — a traditional delicacy (excellent with chinese tea) found in Singapore, Malaysia.

Continuing from my previous post, i thought to document an interpretation of a solution to logging using cats-effect and how another open source effort from the cats-effect ecosystem has helped me further and i believe it could help you too.

First, let’s frame the context and problem. The context is that i’ve been using cats library and its ecosystem of libraries (and contributed to cats) for a number of years (did a talk in 2017 to share my experience with the Singapore Scala community and evangelise cats, follow this if you like) and a common problem i ended up building several times is logging.

The Writer Monad is what i reached out whenever i want to implement logging traversing computations, state because its easy to use and i started exploring ways to integrate the writer monad into the IO monad. When you are starting to learn something new, its absolutely common to experiment and you should not fear about experimenting things but first let’s establish a basis in which to study. The code snippet below represents the fibonacci sequence in IO monad:

def fib(n: Int, a: Long = 0, b: Long = 1)
(implicit cs: ContextShift[IO]): IO[Long] =
IO.suspend {
if (n == 0) IO.pure(a) else {
val next = fib(n - 1, b, a + b)
// Every 100 cycles, introduce a logical thread fork
if (n % 100 == 0)
cs.shift *> next
else
next
}
}

It’s a lazily evaluated computation (via suspend) and the computation allows another thread to handle its computation (via shift which is analogous to moving the evaluation). So, how can i traced this computation ? That is, i want to capture any significant observations i saw in the logic.

Here’s one rendition (i have a snapshot of the code i/o formatting it here because it does not fit well and might hinder your reading) and if you like to read the full source code, follow this:


What is happening here is that the Writer Monad is being passed along the way and each step is being recorded (you can see this from the writer.tell(...) ). You might wonder what’s the essential difference between say IO(println("some log statement")) or even println("some log statement) ? The key difference is that this approach allows the developer to collect all the logs in sequence and leaves her/him the flexibility to push these logs to any data consumer downstream.


Coming back … so it wasn’t that hard and if i have to get both the value and logs written then i would run:

fibW(100).unsafeRunSync.value
fibW(100).unsafeRunSync.written

Naturally, if you wish to pass those logs to a scala logger implementation so that a de-centralized logging framework e.g. logstash, scalyr can pick them up for analysis, whatever.

This approach seemed good … and it gave me flexibility in going one way, and i was interested in whether there was another approach which allowed me to directly leverage a scala logger implementation, preferably something that’s open sourced since this is a common problem ?

Turns out Chris Davenport created the log4cats project which leverages the Scala logger. Awesome! Next, i started looking at what i can do with log4cats, here’s my rendition and you can follow the full source code:

// Describe the fibonacci with loggingdef fib[F[_]:Sync:ContextShift](n: Int, a: Long = 0, b: Long = 1): F[Long] = 
Sync[F].suspend {
if (n == 0) Logger[F].info(s"=> Done $a") *> Sync[F].pure(a)
else {
val next = Logger[F].info(s"=> Next $a") *> fib(n - 1, b, a + b)

// Every 100 cycles, introduce a logical thread fork
if (n % 100 == 0)
Logger[F].info(s"Context Shift!") *> ContextShift[F].shift *> next
else next
}
}
// create a IO taskdef fibL[F[_]:Sync:ContextShift](n: Int, a:Long = 0, b:Long = 1) =
Logger[F].info("Logging Started.") *>
fib(n, a, b) >>=
{ result =>
Logger[F].info("Logging Ended.") *> Sync[F].pure(result)
}
// run the IO taskfibL[IO](100).unsafeRunSync

This work by log4cats is awesome because, it gave developers another way to flush logs downstream directly, if the developer chooses to do so.


Raymond Tay

Written by

Head of Engineering at Thales Digital Factory, Singapore. Author of 'OpenCL Parallel Programming cookbook' & 'Developing an Akka Edge'.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade