In this short blog post I will try, in 10 minutes or less, to present what Monix library is and convince you that it is good to know it.
Formerly known as Monifu, Monix is a library for asynchronous programming in Scala and Scala.js
It contains several abstractions that are useful and sometimes can be found superior to vanilla Scala or Akka counterparts. But for sure this post is not about deprecating use of Akka actors or streams. It is about another tool in Scala programmers box. I am going to present some of the abstractions that Monix gives and conclude why they are valuable.
Task vs Future
Monix comes with
Task abstraction that can be seen as a replacement for
Future. However, there are some major differences.
Future was designed to be ongoing computation that is possibly finished,
Task is a description of computation. So when using
Task as a drop in replacement for
Future, one has to trigger its execution to start computation. This means that following code will not print anything:
To start execution one has to do two things:
- get instance of
- call one of
foreachto execute computation
Scheduler is an
ExecutionContext that additionally allows scheduling single or periodical execution of
CancelableFuture allow programmer to attempt cancellation of some computation before is ends, when it is desired to stop it. That is the second major difference I see and it is a nice improvement in my opinion.
Monix allows high level of control over
Task execution. One can choose if task body should be executed on each invocation of
Task.eval) or if result should be memoized (
memoize) or if execution should always execute in separate thread (
Task.fork). On top of that
Taskhas an API to choose scheduler it should run on.
Let me show one, contrived example, that shows some errors handling and schedulers choice. Code below gets
Picture and should perform analysis that tells if given picture was painted by Picasso or not.
First, it runs analysis locally, but in case when the local algorithm cannot decide, remote analysis service is used. Finally, original picture and analysis results are stored in a database. Of course, services are just mocks, thus results are hardcoded for sake of simplicity.
I have mentioned that
Schedulers are like
ExecutionContext with an ability to schedule execution on them. But there is one more important trait
Scheduler has, namely
ExecutionModel which describes if tasks should be executed in separate logical threads or if trampoline should be used or if batch of tasks should be executed synchronously and then a new thread should be used.
Just a few more words about
Task. It comes with plenty of combinators and constructors, counterparts for both
Future and Scalaz
Task combinators. I will just few that grabbed my attention:
onErrorRestart– restart until completes successfully
restartUntil– restart until result fulfills predicate
timeout– adds timeout to existing
Task, Monix gives you also
FutureUtils.timeoutto timeout Scala
To recap: Monix
Task gives users a great dose of control over asynchronous computation execution by a rich set of builders and combinators, ability to tune schedulers and execution models and also by out-of-the-box possibility to timeout or cancel computations. From my point of view this is very considerable profit.
There are two artifacts available:
monix-scalaz72 with code that turns
Task into an instance of
Monad from Cats or Scalaz.
Coeval is synchronous counterpart for
Task, it gives control over evaluation, side effects and error handling, all at once, but for synchronous computations.
Task it does not run when defined but only when
run is called. One can define
Coeval to execute on each
value call with
to memoize result (like
lazy val). This level of control allows one to remove stack overflows caused by tail recursion.
@tailrec can do that also but only for simple tail recursion that involves one function.
Coeval has many builders, combinators (
restartUntil, error handing and others) and transformers (from/to
Again a bit made up example, this time user will be prompted to enter a number. User enters invalid line 3 times, other prompt will be used and in case of error program falls back to 42.
Cats and Scalaz integrations contain
Monad instances of
Coeval (and other tools mentioned here).
Before I write a really short part about
MVar: I have never written “real” code that used this or analogical abstraction.
take something from
MVar but both operations asynchronously block, what is expressed in return types of
Task[A] respectively. When thread
takes something from empty
MVar it gets
Task[A] that will complete when some thread
puts value to it.
putbehaves similar, returns
Task that completes only after
MVar put was executed (what requires access to empty
Obligatory example, this time dummy producer-consumer problem.
Here is more verbose version with some
printlns to show lazy behaviour of
Task as well.
MVar is inspired by Haskell’s MVar but original blocks thread instead of returning asynchronous computation abstraction (
Task). That is because threads in Haskell are not so scarce.
Last part of the library I would like to mention in this article is Monix’s take on reactive streams.
I will describe it shortly and only from perspective of user, not implementer of new
Observable is equivalent of
org.reactivestreams.Publisher or akka-streams
Observer is like
Consumer, specifies how to consume an
Observable has wealth of combinators: for grouping, filtering, mapping, taking, sliding, giving control when upstream or downstream are slow, attaching callbacks for upstream events like stream end, error handling and more. I am not going to enumerate these but I can assure that library author gave extraordinary effort in that matter.
Provided set of
Consumers is sufficient, I was able to find replacement for each
Sink that I have used in real project based on akka-streams.
I will show what unfolded when I took an akka-stream used in a recent project I worked on and rewritten it with
Monix. I have changed business methods to dummy ones.
First, natural language description of processing, then
akka-streamsimplementation and the last will be Monix implementation.
- Starting point is some equivalent of reactive streams publisher of Kafka “commitable messages” (combination of payload and means to commit offset).
- The message is matched and acted upon. This part is very abstract in following code.
- To avoid excessive offsets commits (which are expensive I/O), offsets are commited only once per 100 messages or 1 second, when traffic is lower.
- Then stream goes through an injectable trigger that can be used to cancel processing.
- There is no element or statistic about the stream that we are interested in, thus we ignore what came through cancel switch.
Full example is available in GH repo.API looks similar to some extent. The main difference is that Monix streams are run on
Scheduler when akka-streams requires
Materializer, which most probably is
ActorMaterializer, which in turn requires
Scheduler is much more lightweight than
I have also written and ran some JMH benchmarks for totally made up set of operations although I will not summarize them as a comparison to avoid possibly false conclusions. Surely Monix streams are very efficient.
Coeval there is glue library that provides Cats or Scalaz Monads instances for
So why should you know Monix?
Because it gives you a very good set of tools for asynchronous programming. There is
Task as an alternative to
Future, with many combinators and helpers to write your asynchronous code. It provides well engineered and efficient implementation of reactive streams.
MVar type can sometimes be the simplest solution for synchronization problems.