Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overview and Resource Safety section of documentation #136

Merged
merged 18 commits into from
Jun 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 18 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,77 +2,41 @@

[![Maven Central](https://img.shields.io/maven-central/v/io.monix/monix-bio_2.12.svg)](https://search.maven.org/search?q=g:io.monix%20AND%20a:monix-bio_2.12)
[![Join the chat at https://gitter.im/monix/monix-bio](https://badges.gitter.im/monix/monix-bio.svg)](https://gitter.im/monix/monix-bio?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Snapshot](https://img.shields.io/nexus/s/https/oss.sonatype.org/io.monix/monix-bio_2.12.svg)](https://oss.sonatype.org/content/repositories/snapshots/io/monix/monix-bio_2.12/)

Alternative to [monix.eval.Task](https://monix.io/api/3.1/monix/eval/Task.html) from [Monix](https://github.com/monix/monix) which uses a second type parameter to represent recoverable errors.
Asynchronous data type with typed errors.
An enhanced version of [monix.eval.Task](https://monix.io/api/3.1/monix/eval/Task.html).

[Documentation Website](https://monix.github.io/monix-bio/)
[Visit Documentation Website](https://monix.github.io/monix-bio/)

## Getting Started
## The latest version

The latest stable version, compatible with Monix 3.x, Cats 2.x and Cats-Effect 2.x:

```scala
libraryDependencies += "io.monix" %% "monix-bio" % "0.1.1"
```
Published for ScalaJS 0.6.x, 1.x, Scala 2.12 and 2.13.

## Short introduction

`BIO[E, A]` represents a specification for a possibly lazy or asynchronous computation, which when executed will produce
a successful value `A`, an error `E`, never terminate or complete with a terminal (untyped) error.

It composes very well and can handle many use cases such as cancellation, resource safety, context propagation, error handling or parallelism.

There are two type aliases:
- `type UIO[A] = BIO[Nothing, A]` which represents an effect which never fails.
- `type Task[A] = BIO[Throwable, A]` - an effect that can fail with a `Throwable` and is analogous to Monix `Task`.

Usage example:

```scala
case class TypedError(i: Int)

// E = Nothing, the signature tells us it can't fail
val taskA: UIO[Int] = BIO.now(10)
.delayExecution(2.seconds)
// executes the finalizer on cancelation
.doOnCancel(UIO(println("taskA has been cancelled")))

val taskB: BIO[TypedError, Int] = BIO.raiseError(TypedError(-1))
.delayExecution(1.second)
// executes the finalizer regardless of exit condition
.guarantee(UIO(println("taskB has finished")))

// runs ta and tb in parallel, takes the result of the first
// one to complete and cancels the other effect
val t: BIO[TypedError, Int] = BIO.race(taskA, taskB).map {
case Left(value) => value * 10 // ta has won
case Right(value) => value * 20 // tb has won
}
## Current Status

// The error is handled and it is reflected in the signature
val handled: UIO[Int] = t.onErrorHandle { case TypedError(i) => i}
The project maintains binary compatibility in `0.1.x` line and it is suitable for production usage.

// Nothing happens until it runs, returns -1 after completion
val f: CancelableFuture[Int] = handled.runToFuture

// => taskB has finished
// => taskA has been cancelled
```
`BIO` covers full `monix.eval.Task` API.

## Current Status
The documentation is in progress and not all scaladocs are updated for `BIO`.

The project will maintain binary compatibility in `0.1.x` line.
It is suitable for general usage but I would not recommend building a lot of libraries on it just yet unless you are fine with upgrade in few months.
## Plans for the future

WARNING: Not all scaladocs are updated for `BIO` and currently there is no other documentation on the website.
It will be my priority in April.
Until then I consider the project as something for more advanced user who are already familiar with idea of IO Monad and error type exposed in the type parameter.
Long term `1.0.0` version is expected to come around July-August 2020 once the basic documentation is complete.

### Plans for the future
We are considering [a different encoding](https://github.com/monix/monix-bio/issues/6) but if we don't go forward with it there won't be many changes.

I hope to gather some feedback from production usage and then move towards long term stable version.
Core of the functionality is shared with `monix.eval.Task` (which is stable) and will remain consistent with it.
There might be breaking changes in regards to new error handling combinators, terminology, return types or error reporting.
Upcoming features (either `1.0.0` or later):
- `reactive` module to use `monix.reactive.Observable` with `monix.bio.BIO`
- built-in interop with `monix.eval.Task` without any imports
- `Coeval` with typed errors
- better stack traces

## Contributing

Expand All @@ -83,15 +47,6 @@ or just ask me on gitter and I should be able to find something regardless of yo

I'm happy to mentor anyone interested in contributing.

## Performance

At the time of writing (Q1 2020) performance is as good as `monix.eval.Task` in most benchmarks and `BIO` can outperform it for error handling operators if the error type is not `Throwable`.
It makes it the most performant effect type in today's Scala ecosystem.

Performance is high priority for us and we will greatly appreciate if you open an issue or write on [gitter](https://gitter.im/monix/monix) if you discover use cases where it performs badly.

You can find benchmarks and their results inside [benchmarks module](benchmarks).

## Credits

Most of the code comes from [Monix](https://github.com/monix/monix) which was customized to include support for error type parameter directly in the internals.
Expand Down
94 changes: 94 additions & 0 deletions docs/comparison.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
id: comparison
title: Other Effects
---

In this section, I will share my thoughts on `BIO` in comparison to other similar solutions at the time of writing (June 2020).

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!

## 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 also contribute to Cats-Effect (including us) 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.

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 a `ContextShift` (for any concurrent operator) or a `Timer` (for sleeping) depending on the situation. It also heavily depends on `Cats` syntax which requires basic familiarity with the library and type class hierarchy.
- *Better Thread Management*. Cats IO is missing an operator like [executeOn](http://localhost:3000/monix-bio/api/monix/bio/BIO.html#executeOn(s:monix.execution.Scheduler,forceAsync:Boolean):monix.bio.BIO[E,A]) 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 `BIO.async` because it can guarantee that
the `BIO` will continue on the default `Scheduler` 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.
- *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.
For instance, compare `monixSequence` and `catsSequence` in [benchmark results](https://github.com/monix/monix-bio/tree/master/benchmarks/results).

Choose for yourself:
- *Typed errors.* Although there is still `Monix Task` if you like the rest.

### Summary

Monix is `cats.effect.IO` with more stuff.
Considering the direction on CE3, `IO` gravitates closer towards the design of Monix.

## 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.
- *Referential transparency.* Take a look at [this classic post](https://www.reddit.com/r/scala/comments/8ygjcq/can_someone_explain_to_me_the_benefits_of_io/e2jfp9b/).
- *No ExecutionContext being dragged everywhere.* Monix only requires its `Scheduler` (the equivalent of `ExecutionContext`) at the point of execution which drastically reduces the temptation to `import ExecutionContext.Implicits.global`.
- *Resource Safety.* `Resource` and `Bracket` are convenient tools that ensure your application doesn't leak any resources.
- *Cats-Effect ecosystem.*

Choose for yourself:
- *Cancellation.* Unlike `Future`, `BIO` 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 `BIO` 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 `BIO` far more pleasant to use.

If you don't, then `Task` might be simpler as well as closer to Scala's `Future`. 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 from `Cats` which increases jar sizes and makes Monix slower to upgrade to new Scala versions.

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.
- *Performance.* Just look at [benchmark results](https://github.com/monix/monix-bio/tree/master/benchmarks/results).
- *Stability.* Monix is definitely more mature library. It also does less which helps in keeping it stable.
This point is mostly about `Monix Task` because `BIO` is a new thing, but it shares like 90% of `Task` so I'd argue the point is still relevant.

Choose for yourself:
- *ZIO Environment.* ZIO adds an extra type parameter (`R`) for dependency management.
Monix favors a classic approach (passing dependencies as parameters, any DI library, or `F[_]`).
If you don't like `Zlayer`, you can pretend `R` doesn't exist, but you will encounter it a lot in type signatures and all `ZIO` libraries use it.
- *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.
- *Innovation.* In recent history, ZIO tends to bring many new ideas, and Monix is more conservative and puts more emphasis on stability.
At the time of writing (19th June 2020), ZIO is yet to release a stable version, but it's ahead in features.

### 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.
99 changes: 98 additions & 1 deletion docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,101 @@ id: overview
title: Overview
---

overview
`BIO[E, A]` represents a specification for a possibly lazy or asynchronous computation.
When executed, it will produce a successful value `A`, an error `E`, never terminate or complete with a terminal (untyped) error.

`BIO` handles concurrency, cancellation, resource safety, context propagation, error handling, and can suspend effects.
All of this makes it simple to write good, high-level code that solve problems related to any of these features in a safe and performant manner.

There are two type aliases:
- `type UIO[A] = BIO[Nothing, A]` which represents an effect that can only fail with terminal errors due to abnormal circumstances.
- `type Task[A] = BIO[Throwable, A]` - an effect that can fail with a `Throwable` and is analogous to `monix.eval.Task`.

[More about errors here.](error-handling)

`Monix BIO` builds upon [Monix Task](https://monix.io/api/3.2/monix/eval/Task.html) and enhances it with typed error capabilities.
If you are already familiar with `Task` - learning `BIO` is straightforward because the only difference is in
error handling - the rest of API is the same.
In many cases, migration might be as simple as changing imports from `monix.eval.Task` to `monix.bio.Task`.

## Usage Example

```scala mdoc:compile-only
import monix.bio.{BIO, UIO}
import monix.execution.CancelableFuture
import scala.concurrent.duration._

// Needed to run BIO, it extends ExecutionContext
// so it can be used with scala.concurrent.Future as well
import monix.execution.Scheduler.Implicits.global

case class TypedError(i: Int)

// E = Nothing, the signature tells us it can't fail
val taskA: UIO[Int] = BIO.now(10)
.delayExecution(2.seconds)
// executes the finalizer on cancelation
.doOnCancel(UIO(println("taskA has been cancelled")))

val taskB: BIO[TypedError, Int] = BIO.raiseError(TypedError(-1))
.delayExecution(1.second)
// executes the finalizer regardless of exit condition
.guarantee(UIO(println("taskB has finished")))

// runs ta and tb in parallel, takes the result of the first
// one to complete and cancels the other effect
val t: BIO[TypedError, Int] = BIO.race(taskA, taskB).map {
case Left(value) => value * 10 // ta has won
case Right(value) => value * 20 // tb has won
}

// The error is handled and it is reflected in the signature
val handled: UIO[Int] = t.onErrorHandle { case TypedError(i) => i}

// Nothing happens until it runs, returns -1 after completion
val f: CancelableFuture[Int] = handled.runToFuture

// => taskB has finished
// => taskA has been cancelled
```

## Target audience

The target audience of `BIO` are users of `cats.effect.IO`, `monix.eval.Task`, and `Future` who tend to use `EitherT` a lot
and would like to have a smoother experience, with better type inference, no syntax imports, and without constant wrapping and unwrapping.

If you are completely new to effect types, I'd recommend to start with `cats.effect.IO`, or `monix.eval.Task`,
but if you really like the concept of typed errors then there is nothing wrong to go for `monix.bio.BIO`, or `zio.ZIO` from the start.

## Motivation

DISCLAIMER: The following part is very subjective opinion of the author.

There are already many effect types in Scala, i.e. [cats.effect.IO](https://github.com/typelevel/cats-effect), [Monix Task](https://github.com/monix/monix), and [ZIO](https://github.com/zio/zio).
It begs a question - why would anyone want another one?

It seems like built-in typed errors have warm reception, and the only other effect which has built-in typed errors is `ZIO`.
Not everyone likes everything about `ZIO` and I feel like there are enough differences in Monix` to make it a valuable alternative.
For instance, if you are a happy user of Typelevel libraries (http4s, fs2, doobie etc.) you might find that `BIO` has a nicer integration and it is more consistent with the ecosystem.
[More differences here.](comparison)

### Monix Niche

To me, the big difference between Monix and other effect libraries is its approach to impure code.
Both `cats.effect.IO` and `zio.ZIO` will push you to write a 100% purely functional codebase, except for isolated cases where low-level imperative code is needed for performance.
I would say Monix is as good as other effects for pure FP, but we go the extra mile to provide support for users who would rather go for a hybrid approach, or are allergic to purely functional programming.
Here are few examples of Monix providing extra support for users of `Future`:
- The `monix-execution` module provides many utilities to use with `Future` even if you're not interested in `Task` at all.
- Monix uses a `Scheduler` which is also an `ExecutionContext` and can be used with `Future` directly.
- `Local` works with both `Future` and Monix `Task/BIO`.

In other words, Monix aims to help with impure code too (if you choose to do so), rather than treating it as a temporary nuisance which waits for a rewrite.

## Performance

At the time of writing (Q1 2020) performance is as good as `monix.eval.Task` in most benchmarks and `BIO` can outperform it for error handling operators if the error type is not `Throwable`.
It makes it the fastest effect type in today's Scala ecosystem.

Performance is a high priority for us, and we will greatly appreciate if you open an issue or write on [gitter](https://gitter.im/monix/monix) if you discover use cases where it performs badly.

You can find benchmarks and their results inside [benchmarks module](https://github.com/monix/monix-bio/tree/master/benchmarks).
Loading