Monix

Monix’ Task can be used as a target of your FreeS programs.

import freestyle._

We will use an example similar to the one used in the parallelism section. We will create a small algebra to validate numbers:

@free trait Validator {
  def isPositive: FS[Boolean]
  def isEven: FS[Boolean]
}
// defined trait Validator
// defined object Validator

We can assert that something should be positive and even by combining both operations:

import cats.syntax.cartesian._
// import cats.syntax.cartesian._

def isPositiveEven[F[_]](implicit V: Validator[F]): FreeS.Par[F, Boolean] =
  (V.isPositive |@| V.isEven).map(_ && _)
// isPositiveEven: [F[_]](implicit V: Validator[F])freestyle.FreeS.Par[F,Boolean]

Our algebra didn’t specify exactly what will be validated, so that gives us the liberty to do that in a handler.

We will create a handler that validates integers and can potentially do so asynchronously.

We can encode that we will validate integers by using Reader[Int, ?] and the (potential) asynchronicity with Monix Task, combining the two we end up with ReaderT[Task, Int, ?] or Kleisli[Task, Int, ?].

import cats.data.Kleisli
// import cats.data.Kleisli

import monix.eval.Task
// import monix.eval.Task

type ValidateInt[A] = Kleisli[Task, Int, A]
// defined type alias ValidateInt

def blockAndPrint(millis: Long, msg: String): Unit = { 
  Thread.sleep(Math.abs(millis))
  println(msg)
}
// blockAndPrint: (millis: Long, msg: String)Unit

implicit val validateIntTaskHandler: Validator.Handler[ValidateInt] = 
  new Validator.Handler[ValidateInt] {
    def isPositive: ValidateInt[Boolean] =
      Kleisli(i => Task.eval { blockAndPrint(i * 500L, s"isPositive($i)"); i > 0 })

    def isEven: ValidateInt[Boolean] =
      Kleisli(i => Task.eval { blockAndPrint(i * 250L, s"isEven($i)"); i % 2 == 0 })
  }
// validateIntTaskHandler: Validator.Handler[ValidateInt] = $anon$1@5397cf03

With the Freestyle implicits and the Monix to Cats conversions in scope, we can use our handler to interpret the isPositiveEven program:

import freestyle.implicits._
// import freestyle.implicits._

import monix.cats._
// import monix.cats._

val check = isPositiveEven[Validator.Op].interpret[ValidateInt]
// check: ValidateInt[Boolean] = Kleisli(cats.data.Kleisli$$Lambda$3781/1207336948@619403dd)

We can pass some integers to the check program with the Kleisli#run method and run the Task as a Future using Task#runAsync:

import monix.execution.Scheduler.Implicits.global
// import monix.execution.Scheduler.Implicits.global

import scala.concurrent.Await
// import scala.concurrent.Await

import scala.concurrent.duration.Duration
// import scala.concurrent.duration.Duration

Await.result(check.run(1).runAsync, Duration.Inf)
// isPositive(1)
// isEven(1)
// res0: Boolean = false

Await.result(check.run(2).runAsync, Duration.Inf)
// isPositive(2)
// isEven(2)
// res1: Boolean = true

Await.result(check.run(-1).runAsync, Duration.Inf)
// isPositive(-1)
// isEven(-1)
// res2: Boolean = false

We can see that isPositive is always printed before isEven even though isEven doesn’t take as long as isPositive.

This happens because, in most Monad instances, the Applicative operations are implemented using flatMap, which means that the operations are sequential.

Monix however, also has a nondeterministic Monad instance, that will execute Tasks in parallel:

import Task.nondeterminism
// import Task.nondeterminism

val check2 = isPositiveEven[Validator.Op].interpret[ValidateInt]
// check2: ValidateInt[Boolean] = Kleisli(cats.data.Kleisli$$Lambda$3781/1207336948@645b31dc)

Await.result(check2.run(1).runAsync, Duration.Inf)
// isEven(1)
// isPositive(1)
// res3: Boolean = false

Await.result(check2.run(2).runAsync, Duration.Inf)
// isEven(2)
// isPositive(2)
// res4: Boolean = true

Await.result(check2.run(-1).runAsync, Duration.Inf)
// isEven(-1)
// isPositive(-1)
// res5: Boolean = false

Note that if we lift our isPostiveEven program into FreeS, it will still execute sequentially. This is an issue in the Monad instance of Kleisli in the current Cats version, that means that it doesn’t use (all) the Applicative methods of the nondeterministic instance of Task. In the next release of Cats, this will execute in parallel as well.

val check3 = isPositiveEven[Validator.Op].freeS.interpret[ValidateInt]

Await.result(check3.run(1).runAsync, Duration.Inf)
Await.result(check3.run(2).runAsync, Duration.Inf)
Await.result(check3.run(-1).runAsync, Duration.Inf)

Async

There is also the freestyle-async-monix module mentioned in the async callback section, which allows you to work with callback-based APIs in freestyle and translate your FreeS program in the end to a type like Monix’ Task.