Other Effects
The following section shares the author's opinion about monix.bio.IO
in comparison to other similar solutions at the time of writing.
Keep in mind it can quickly become outdated. If you have any ideas for possible reasons for, or against Monix then please contribute them to this section. It's important to me to limit bias to the minimum, and it's hard without an outsider's perspective!
Last Update: September 2020
Cats-Effect IO
Why Cats-Effect IO:
- Official Cats-Effect implementation. Typelevel libraries use it in tests, so you are the least likely to encounter bugs. Many maintainers of related libraries also contribute to Cats-Effect (including Monix) so there is a big and diverse pool of potential contributors.
- Minimal API.
cats.effect.IO
is missing many operators in the API as well as a number of features. However, if you don't need them (e.g. you are a user of Tagless Final), then the smaller API surface can be an advantage. - Stack traces. Cats IO added better stack traces in 2.2.0 release which are still missing in Monix.
Why Monix:
- Richer API. Monix has a larger, more discoverable API (no syntax imports).
- Fewer implicits. Monix only needs a
Scheduler
when executing the effect. Cats-Effect IO needs aContextShift
(for any concurrent operator) or aTimer
(for sleeping) depending on the situation. It also heavily depends onCats
syntax which requires basic familiarity with the library and type class hierarchy. - Better Thread Management. Cats IO is missing an operator like executeOn
which ensures that the task will execute on a specified thread pool.
Cats IO can only guarantee it up to the first asynchronous boundary.
Monix is also safer when using Callback-based builders like
IO.async
because it can guarantee that theIO
will continue on the mainScheduler
regardless of where the callback is called. - Local. Monix has a way to transparently propagate context over asynchronous boundaries which is handy for tracing without polluting the business logic.
- Better integration with Scala's Future. We can use the same
Scheduler
and tracing libraries for both. This is important when the code base uses libraries from other ecosystems, such as Akka HTTP. - Slightly higher performance. In a lot of cases, performance is the same, however Monix provides a lot of hand-optimized methods that are derived in Cats IO, and thus, slower.
For instance, compare
monixSequence
andcatsSequence
in benchmark results.
Depends on your preference:
- Typed errors.
They can bring extra complexity because of two error channels in the implementation and there are other ways to handle errors
that can be as good, or better depending on your preference.
For example: Encoding errors and successes in the same ADT, using
EitherT
locally, keeping a single error channel with global error handler, and Cats MTL. Although there is stillMonix Task
if you like the rest of the differences. - Final Tagless. Many Cats-Effect examples are biased towards its usage so if you prefer concrete effects, it is a noise.
Summary
Until recently, Monix was cats.effect.IO
with more stuff, better Future
interop and optional typed errors.
Since 2.2.0 release, Cats IO includes better stack traces that might take few months before coming to Monix.
Also note that many features that are missing in cats.effect.IO
will come in CE3 release and the design of both
libraries will most likely stay very consistent.
I expect that the main differences will come from the approach to Future
support and mixed codebases,
and an option for the extra type parameter.
Scala's Future
Why Future:
- It is in the standard library. The safest bet in terms of stability and most Scala developers know it well.
- Minimal API. If it's good enough for your use cases, why bother to learn anything more?
Why Monix:
- Easier concurrency. Tons of extra operators and better composability due to laziness. Parallelism is explicit.
- Referential transparency. Take a look at this classic post.
- No ExecutionContext being dragged everywhere.
Monix only requires its
Scheduler
(the equivalent ofExecutionContext
) at the point of execution which drastically reduces the temptation toimport ExecutionContext.Implicits.global
. - Resource Safety.
Resource
andBracket
are convenient tools that ensure your application doesn't leak any resources. - Cats-Effect ecosystem.
Depends on your preference:
- Cancellation. Unlike
Future
,IO
can be cancelled. It sounds awesome, but it's no magic, and it's not possible to stop an effect immediately at an arbitrary point in time. Sometimes cancellation can be tricky, and many projects don't have any use case for it at all. - Typed errors.
Monix Task
The only difference between Monix Task
and IO
are typed errors - Both the API and performance are very consistent.
If you use Task[Either[E, A]]
or EitherT
a lot - you might find IO
far more pleasant to use.
If you don't, then Task
might be simpler as well as closer to Scala's Future
if that's what you're coming from.
It also has better integration with Observable
.
ZIO
Why ZIO:
- Bigger community and ZIO-specific ecosystem.
- Better stack traces. Killer feature. Hopefully it will come to Monix this year but until then it's a huge advantage for ZIO.
- More flexible with relation to cancellation.
Monix has an
uncancelable
operator but unlike ZIO, it doesn't have the reverse. ZIO has also implemented structured concurrency for fibers. - Fewer dependencies.
Monix depends on
Cats-Effect
which brings a ton of redundant syntax and instances fromCats
which increases jar sizes and makes Monix slower to upgrade to new Scala versions. - More innovation. Over the last few years, ZIO brought many great ideas that were followed by Monix and Cats.
Why Monix:
- Better Cats-Effect integration. Monix depends on Cats-Effect directly and its instances are available without any extra imports. We are also very consistent in the implementation, so it is rare to encounter a bug when using a Cats-Effect based library which is exclusive to Monix.
- Better integration with Scala's Future. We can use the same
Scheduler
and tracing libraries for both. This is important when the code base uses libraries from other ecosystems, such as Akka HTTP. - Performance. See benchmark results.
- Stability. Monix is definitely more mature library. It also does less which helps in keeping it stable.
This point is mostly about core Monix because
BIO
is a new thing, but it shares like 90% ofmonix.eval.Task
so I'd argue the point is still relevant.
Depends on your preference:
- ZIO Environment. ZIO adds an extra type parameter (
R
) for dependency management. Monix favors the classic approach (passing dependencies as parameters, any DI library, orF[_]
). You don't have to useR
but it will come up in the signatures andZIO
libraries use it so you will be programming "against the tide". - Framework experience vs pluggable library.
ZIO is more opinionated and pushes you to use
ZIO
-everything, otherwise you might have an underwhelming experience. Monix wants to naturally interop with both FP and non-FP ecosystem but it might have less "batteries" included. ZIO can be better at forcing you to follow a specific pattern of programming, while Monix can play nicer with your team's individual preferences and mixed (e.g. using both Monix and Future) codebases.
Summary
ZIO has more contributors, more features (which you may or may not ever use), is more opinionated, and develops a new ecosystem of libraries which are tailored to ZIO needs. Monix is more stable, faster and puts more focus on integrating with existing ecosystems, rather than trying to create a new one.