Why use Cats IO instead of Scala Futures?

future to IO
Reading Time: 2 minutes

This article focuses on using Cats-Effects IO. Cats effects can be applied to other IO libraries as well such as Monix. We are discussing Future to IO conversion in this blog.

Main benefits of using IO:

  • IO is pure, immutable and referentially transparent.
  • It makes it easy to control the execution of the effects.
  • It’s currently the most common solution to reflect the pure and impure parts of the program.
  • Fits in the Cats functional programming ecosystem.
  • A value of type IO is a computation that, when evaluated, can perform effects before returning a value.

What is an effect/side-effect?

A side effect is a function or expression which mutates the state of something outside its local environment. It can be something as simple as printing to console, making a Database call or IO (Input/Output) operation.

Referential Transparency

It is a property of purely functional languages that says an expression always gets evaluated to the same result and that the expression itself can be replaced by its result without changing the behaviour of the program. e.g.

val x = 10
val y = x + x    // 20
and
val z = 10 + 10  // 20 
// are equivalent, hence referentially transparent

Future to IO Conversion

Let’s use the following method returning a Scala Future:

private def getCustomerByIdFuture(id: Int): Future[String] = {
  Future {
    println(s"Future function for $id evaluated at ${LocalTime.now()}")
    s"Customer_$id"
  }
}

This method has a side effect as it prints to the standard output and retrieves the current time.
This method could perform a DB operation, a REST call; the point is that it’s a method returning a side effect wrapped in a Future; something older / legacy database or REST libraries would return.

And we wrap it into the IO and to highlight the problem let’s add 2 seconds sleep:

private def badGetCustomerByIdIo(id: Int): IO[String] = {
  val future = getCustomerByIdFuture(id)

  for {
    customer <- IO.fromFuture(IO(future))
    _        <- IO.sleep(2 seconds)
  } yield { customer }
}

The method returns an IO, as what we required. We can use it and it works seemingly fine:

private def goodGetCustomerByIdIo(id: Int): IO[String] = {
  // val future = futureFunctionWithSideEffect(id)

  for {
    customer <- IO.fromFuture(IO(getCustomerByIdFuture(id)))
    _        <- IO.sleep(2 seconds)
  } yield { customer }
}

If we relly want to perform result in syncronous way, then we can follow this

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import cats.effect.IO

def print(message: => String): Future[Unit] =
  Future {
    Thread.sleep(2000)
    println(message)
  }

def greet: IO[Unit] =
  for {
   _ <- IO.fromFuture(IO(print("First")))
   _ <- IO.fromFuture(IO(print("Second")))
  } yield ()

greet.unsafeRunAsyncAndForget()

This will not start the second IO / Future until the first one completes.

Conclusion

It’s beneficial to use libraries that are built in the cats/cats-effect ecosystem as it would eliminate/reduce the need of using Future through IO.

Unit testing the timing of the effects in IO or Future is not something application developers usually do. If you want to work on such a code then make sure you at least perform some manual testing to make sure your code is working as expected.

Also visit Here to know more about cats effect.


future

Written by 

Vipul Kumar is Senior Software Consultant having 6years of experience. He has knowledge of languages like Functional Java, Scala, Akka, Akka-http. He is familiar with Object-Oriented Programming Paradigms and also has an interest in Functional Programming technologies. His hobbies include travelling, riding bikes and listening to music.

Leave a Reply