How to be functionally lazy with Monix Coeval?

Reading Time: 4 minutes

Asynchroneous programming and is well known term these days. Most of the applications are using this concept. In the world of big data there are many frameworks and libraries that allows us to write asynchroneous applications. Monix is one of the very famous and widely used library that provide asynchroneous programing for Scala and Scala.js. In this blog we would be talking about how the monix allows to write functioanl lazy code with more controled execution.

According to typelevel monix is a “High-performance library for composing asynchronous, event-based programs, exposing a Reactive Streams implementation along with primitives for dealing with concurrency and side-effects.”

So, if we try to evaluate the monix, we find that the monix is a functional library that provide abstractions to handle the side effects in a better way. The monix has many sub projects linke monix-eval, monix-reative, monix-catnap etc. The monix-eval is focused towards providing purely functional effects in a principled way. It provide two major API’s Coeval and Task. For this blog we will be majorly discussing Coeval and how to leverage lazy evaluation with coeval.

What is Coeval?

The Coeval is a data type for controlling asynchroneous and possibly lazy evaluations, useful for describing lazy expressions and for controlling side effects. The Coeval is a synonym for “synchronous”.

Lets take an example:

package com.knoldus.config.app.coeval

import com.knoldus.config.app.Student
import monix.eval.Coeval
import scala.util.Random

object CoevalSample extends App {

  def average(students: List[Student], i: Int): Int = {
    students.map(_.marks).sum / students.size
  }

  def students: List[Student] = (0 until 100).map{ rollNo =>
    Student(rollNo, Random.nextInt(100))
  }.toList

  val averageCoeval = Coeval.eval(average(students, 1)) // Nothing happens here as coeval is lazy

 averageCoeval.value() // The evaluation happens here
}

As shown above, the coeval is lazy so the actual evaluation does not take place imidiately. The actual evaluation takes place at line:

averageCoeval.value()

Design philosophy of Coeval:

The coeval is very similar to another data type provided by monix which is called Task, but it works for imimdiate and synchroneous evaluations. It can be a replacement for lazy val and by-name parameters in usual Scala. Unlike normal variables, it does not trigger the execution imidiatly and only trigger evaluation when value or run called. It not only allows controlling side effects but also allows error handling. Coeval is like a method only that will be evaluated only if it is requred.

What problem Coeval solves?

  • The coval provide a way to simplify the use of lazy val as being a Scala developer only you can not pass a lazyval to a function and return a lazy val from the function.
  • Simimlarly for by-name parameters proper type is not provided by Scala.
  • The tail recursion is provided by scala but it does not work for mutually tail recursive calls, so its limited.
  • Scala try is similar to Coeval but does not have a lazy behaviour.

So, the Coeval can replace lazy val and by name parameters with control on evaluation and error handling. The coeval is also stack safe, and allows mutually tail recursive algorithms.

Coeval Builders:

There are various builds available for coeval creation depending on various use cases. Here are some most common builders for coeval:

– Coeval.now:

Coeval.now is used to lift an already known value:

val averageCoeval = Coeval.now(average(students)) // Calculated average = 50

– Coeval.eval:

Its similar to Function0 taking function that will always evaluate on invocation of value or run method.

val averageCoeval = Coeval.eval(average(students)) // Nothing happens here as coeval is lazy
averageCoeval.value()//Calculated average = 49

– Coeval.evalOnce:

Coeval.evalOnce is equvalant to lazy val and it does memoization on the first run, such that the result of the evaluation will be available for subsequent runs.

val averageEvalOnce = Coeval.evalOnce(average(students))

averageEvalOnce.value() // Evaluating
averageEvalOnce.value()
averageEvalOnce.value()

– Coeval.defer:

Coeval.defer is used for building factory of coevals. For example this works very similar to Coeval.eval:

val coeval = Coeval.defer{
  Coeval.now(average(students))
}
coeval.value() // Evaluating

– Coeval.raiseError:

Coeval.raiseError can lift the error in monadic way:

val error = Coeval.raiseError[Int](new IllegalStateException)

– Coeval.unit

Coeval.unit is a utility provided to return an already completed Unit type (Coeval[Unit]) like Coeval.now()

val unitCoeval: Coeval[Unit] = Coeval.unit

How to handle errors with Coeval?

Coeval also provide error handling. Monix expect the arguments given to its operators like flatMap to be pure or protected from errors. In terms of catching errors we can use runTry or run. Here is an example for it:

val coeval = Coeval(Random.nextInt).flatMap {
  case even if even % 2 == 0 =>
    Coeval.now(even)
  case odd =>
    throw new IllegalStateException(odd.toString)
}

val result: Int = coeval.runTry match {
  case Success(value) => value
  case Failure(ex) =>
    println("Got error, returning 0")
    0
}

println("Got value : " + result)

The output of above code when the error is there is as folllows.

Got error, returning 0
Got value : 0

Recovering from errors:

The Coeval provide and option to map the exceptions to a desired fallback outcome. Here is an example of handling it with “onErrorHandleWith”:

val coevalError = Coeval.raiseError(new IllegalStateException("Test exception"))

val recoveredResult = coevalError.onErrorHandleWith{
  case _: IllegalStateException => Coeval.now("Recovered")
  case ex => Coeval.raiseError(ex)
}

println("Got result... : " + recoveredResult.value())

There are other varient available for recovering from erros as well like onErrorRecover and onErrorRecoverWith.

Restart on Error:

Apart from it, we can also restart the operation in case there is a failure:

val coevalWithError: Coeval[Int] = Coeval(Random.nextInt).flatMap {
  case even if even % 2 == 0 =>
    println("Calculating...")
    Coeval.now(even)
  case other =>
    println("Error found...")
    Coeval.raiseError(new IllegalStateException(other.toString))
}

coevalWithError.onErrorRestart(10).run()

In above example it will retry until we get a sucessful result till 10 retries. Here is a sample result for the above code:

Error found…
Calculating…

Very similar to that, we also have another option to retry based on specefic conditions:

val randomEven = coevalWithError.onErrorRestartIf {
  case _: IllegalStateException => true
  case _ => false
}

randomEven.run()

This also allows handling different kind of errors and retry basedon error type. In this way monix Coeval provide handling of computations in a functional way.

If you are interested here are some of the good reads for functional programing:
https://blog.knoldus.com/getting-started-with-scala-cats/
https://blog.knoldus.com/higher-order-functions-and-closures-in-scala/
https://blog.knoldus.com/higher-order-functions-and-closures-in-scala/
https://blog.knoldus.com/scala-beginner-series-3-functional-scala/

Reference:

Monix official doc, Image

Hope you enjoyed the post. Thanks for reading!

Written by 

Girish is a Software Consultant, with experience of more than 3.5 years. He is a scala developer and very passionate about his interest towards Scala Eco-system. He has also done many projects in different languages like Java and Asp.net. He can work in both supervised and unsupervised environment and have a craze for computers whether working or not, he is almost always in front of his laptop's screen. His hobbies include reading books and listening to music. He is self motivated, dedicated and focused towards his work. He believes in developing quality products. He wants to work on different projects and different domains. He is curious to gain knowledge of different domains and try to provide solutions that can utilize resources and improve performance. His personal interests include reading books, video games, cricket and social networking. He has done Masters in Computer Applications from Lal Bahadur Shastri Institute of Management, New Delhi.

Leave a Reply