Cats-Effect Integration
Monix-BIO provides Cats-Effect integration out of the box. In practice, it means that integration with Typelevel libraries, such as http4s, or doobie should work without much hassle.
Getting instances in scope
All Cats instances up until Effect and ConcurrentEffect (excluded) are available automatically, without any imports.
import cats.syntax.parallel._
import monix.bio.IO
val taskA = IO(20)
val taskB = IO(22)
// evaluates taskA and taskB in parallel, then sums the results
val taskAplusB = (taskA, taskB).parMapN(_ + _)
ConcurrentEffect and Effect can be derived if there is Scheduler in scope.
Sync and above
Infamous Sync type class extends Bracket[F, Throwable].
Throwable is the error type and as an unfortunate consequence - any type class from Sync and above will only work with IO[Throwable, A].
For instance, let's say we want to use monix.catnap.ConcurrentQueue which exposes an interface built on Cats-Effect type classes so it can be used with any effect:
import monix.bio.{IO, Task}
import monix.catnap.ConcurrentQueue
val queueExample: IO[Throwable, String] = for {
queue <- ConcurrentQueue[Task].bounded[String](10)
_ <- queue.offer("Message")
msg <- queue.poll
} yield msg
The bounded constructor requires Concurrent[F] and ContextShift[F] in scope.
Both requirements are automatically derived by IO, but Concurrent extends Sync, so we need to settle on Throwable error type.
Since our F is IO[Throwable, *], all operations on ConcurrentQueue will return IO[Throwable, *].
A workaround is to use hideErrors because these methods don't throw any errors, and even if they did - how would we handle them?
import monix.bio.{Task, UIO}
import monix.catnap.ConcurrentQueue
val queueExample: UIO[String] = (for {
queue <- ConcurrentQueue[Task].bounded[String](10)
_ <- queue.offer("Message")
msg <- queue.poll
} yield msg).hideErrors
If typed errors prove to be a great idea in the long term, and not just a temporary fashion, new editions of Cats will likely support it more naturally.
Converting from/to other effects
Cats-Effect provides a hierarchy of type classes that open the door to conversion between effects.
Monix-BIO provides:
monix.bio.IOLiketo convert other effects toIOwith niceIO.fromsyntax.monix.bio.IOLiftto convertIOto a different type withio.to[F]syntax.
cats.effect.IO
Going from cats.effect.IO to monix.bio.IO is very simple because cats.effect.IO does not need any runtime to execute:
import monix.bio.IO
val catsIO = cats.effect.IO(20)
val monixIO: IO[Throwable, Int] = IO.from(catsIO)
Unfortunately, we need Scheduler in scope to go the other way:
import monix.bio.IO
import monix.execution.Scheduler.Implicits.global
val monixIO: IO[Throwable, Int] = IO(20)
val catsAgain: cats.effect.IO[Int] = monixIO.to[cats.effect.IO]
monix.eval.Task
monix.bio.IO does not include monix.eval.Task in dependencies, but since they both use Scheduler to run, we can do it
without requiring any implicit in scope, if we use deferAction:
import monix.bio.IO
import monix.eval.Task
val task = Task(20)
val bio: IO[Throwable, Int] = IO.deferAction(implicit s => IO.from(task))
val taskAgain: Task[Int] = Task.deferAction(implicit s => bio.to[Task])
In the future, we might introduce a type class in monix-execution, which will allow this conversion without any tricks with deferAction.
zio.ZIO
To convert from ZIO, you will need ConcurrentEffect[zio.Task] instance.
It can be derived with zio-interop-cats if you have zio.Runtime in scope:
import monix.bio.IO
import zio.interop.catz._
implicit val rts = zio.Runtime.default
val z = zio.ZIO.effect(20)
val monixIO: IO[Throwable, Int] = IO.from(z)
The other direction requires Scheduler in scope:
import monix.bio.IO
import zio.interop.catz._
implicit val s = monix.execution.Scheduler.global
val monixIO: IO[Throwable, Int] = IO(20)
val zioAgain: zio.Task[Int] = monixIO.to[zio.Task]