cats’ IO Monad

As a functional programmer, we want to push the side effects to the border of our program, to reason easier about our program. We want to use pure functions, so when reasoning about a program, we can replace the function call with the result, without changing the meaning of the program.

Using standard Scala futures doesn’t accomplish this because a future is normally executed right away: we don’t have any control over when the execution starts and if it contains a side effect (e.g. access a database or read from a file) this side effect is executed right away.

To complish pureness and to know which part of our programs contain side effects when executed, we can use an IO monad. There are a couple available for Scala. Here we look at the new IO monad for cats,

Using this IO monad we have control over when the side effect is executed. Compare the following two code fragments:

val a = Future {
  println("something)
  5
}

And

val a = IO {
  println("something")
  5
}

In the first case something is printed and in the second case we’ve only described the side effect and not actually run the code. We can run the code using a.unsafeRunSync().

This is something that is typically performed in one place in your program: you normally compose the side effects until you have the complete program and in your main function, you run the side effects.

In code, when you see that a function returns an IO, this means that a side effect is contained in the IO and will be performed when running it. Using an IO monad to contain side effects not only makes the code more functional, it also clearly marks which parts of the code, when executed, will perform side effects.

Composing IO monads is easy: because they are monads, you can use a for comprehension, e.g.:

def getPlayer(): IO[Player] = {
  // Read player from database.
  ...
}

def getHighscore(playerId, gameId: Int): IO[Option[Int]] = {
  // Read high score from database
  ...
}

val result: IO[Option[Int]] = for {
  player <- getPlayer(playerId)
  highscore <- getHighscore(playerId, player.favoriteGameId)
} yield highscore

We have now composed a side effect that gets the highscore for the player’s favorite game.

When using the IO monad, we must be aware not to block the program: if we use a blocking API inside an IO monad, this will block the current thread. When using blocking operations, we need to use a separate execution context for the blocking operation and afterwards switch back to the original execution context.

Using IO.shift we can shift to a different execution context:

def blockingOperation(): String = {
  // Read from file
  ...
}

val result: IO[String] = for {
  _ <- IO.shift(executionContextForBlockingOperations)
  result <- IO { blockingOperation() }
  _ <- IO.shift(mainExecutionContext)
} yield result

The execution context for blocking operations will usually not have a fixed number of threads, unlike the main execution context, but will grow and shrink depending upon the number of blocking operations that are being executed.

Since the 0.9 version of Cats Effect, it is possible to perform multiple operations in parallel, provided that the IO contains an asynchronous operation, see also the parallelism section of the Cats Effect site.

Suppose we want to retrieve all the friends of a player in parallel:

// Enable usage of .parSequence
import cats.syntax.all._

def getPlayer(playerId: Int): IO[Player] = {
  // Read player from database.
  ...
}

val friendIds: NonEmptyList[Int] = player.friendIds
val getPlayers: NonEmptyList[IO[Player]] = friendIds.map(getPlayer)
val friends: IO[NonEmptyList[Player]] = getPlayers.parSequence

We now retrieve all the friends in parallel and combine them in one side effect contained in an IO monad (for simplicity in this example, we assume that players have at least one friend, hence the NonEmptyList. For a normal List, the parSequence isn’t available).

There are several libraries that can be used with cats’ IO Monad. E.g. http4s , which provides functional, streaming HTTP, and doobie, which provides pure functional access to SQL databases, can be used with cats’ IO Monad. Note that all these libraries are generic in which IO Monad they use, so you could e.g. also use the one from Scalaz.

Leave a Reply

Your email address will not be published.