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

Add documentation for Bimonad #4076

Merged
merged 11 commits into from
Dec 24, 2021
85 changes: 85 additions & 0 deletions docs/src/main/mdoc/typeclasses/bimonad.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
layout: docs
title: "Bimonad"
section: "typeclasses"
source: "core/src/main/scala/cats/Bimonad.scala"
scaladoc: "#cats.Bimonad"
---
# Bimonad

The `Bimonad` trait directly extends `Monad` and `Comonad` without introducing new methods. `Bimonad` is
different from other `Bi` typeclasses like `Bifunctor`, `Bifoldable` or `Bitraverse` where the prefix describes
a `F[_, _]`. The `Bimonad` is a `F[_]` and the `Bi` prefix has a different meaning here: it's both a `Monad` and a `Comonad`.
Keep in mind `Bimonad` has its own added laws so something that is both monadic
and comonadic may not necessarily be a lawful `Bimonad`.

If you use `Bimonad` as a convenience type such that:
```scala
def f[T[_]: Monad: Comonad, S](fa: T[S]): S
```
is re-written to:
```scala
def f[T[_]: Bimonad, S](fa: T[S]): S
```
then `T[_]` also needs to respect an extra set of laws.

### NonEmptyList as a Bimonad
`NonEmptyList[_]` is a lawful `Bimonad` so you can chain computations (like a `Monad`) and `extract` the result at the end (like a `Comonad`).

Here is a possible implementation:
```scala mdoc
import cats._
import cats.data._
import cats.implicits._

implicit val nelBimonad =
new Bimonad[NonEmptyList] {

// in order to have a lawful bimonad `pure` and `extract` need to respect: `nelBimonad.extract(nelBimonad.pure(a)) <-> a`
override def pure[A](a: A): NonEmptyList[A] =
NonEmptyList.one(a)

override def extract[A](fa: NonEmptyList[A]): A =
fa.head

// use coflatMap from NonEmptyList
override def coflatMap[A, B](fa: NonEmptyList[A])(f: NonEmptyList[A] => B): NonEmptyList[B] =
fa.coflatMap(f)

// use flatMap from NonEmptyList
override def flatMap[A, B](fa: NonEmptyList[A])(f: A => NonEmptyList[B]): NonEmptyList[B] =
fa.flatMap(f)

// the tailRecM implementation is not the subject of this material
// as an exercise try to implement it yourself
override def tailRecM[A, B](a: A)(fn: A => NonEmptyList[Either[A, B]]): NonEmptyList[B] =
???
}
```

Note the equivalence:
```scala mdoc
nelBimonad.pure(true).extract === NonEmptyList.one(true).head
```

Using generic bimonad syntax we could define a function that appends and extracts a configuration:
```scala mdoc
def make[T[_]: Bimonad](config: T[String]): String =
config
.flatMap(c => Bimonad[T].pure(c + " with option A"))
.flatMap(c => Bimonad[T].pure(c + " with option B"))
.flatMap(c => Bimonad[T].pure(c + " with option C"))
.extract
```

This works with one element non-empty lists:
```scala mdoc
make(NonEmptyList.one("config"))
```

`Function0[_]` and `Eval[_]` are also lawful bimonads so the following calls are also valid:
```scala mdoc
make(() => "config")

make(Eval.later("config"))
```
28 changes: 14 additions & 14 deletions docs/src/main/mdoc/typeclasses/typeclasses.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,20 +238,20 @@ From [cats-infographic by @tpolecat](https://github.com/tpolecat/cats-infographi
Originally from [@alexknvl](https://gist.github.com/alexknvl/d63508ddb6a728015ace53cb70a1fd5d)


| Type | Functor | Apply | Applicative | Monad | MonoidK | ApplicativeError | MonadError | CoflatMap | Comonad |
| --------------- |:-------:|:-----------------:|:-----------:|:-----:|:-------:|:-----------------:|:----------:|:---------:|:-------:|
| Id[A] | ✔ | ✔ | ✔ | ✔ | ✗ | ✗ | ✗ | ✔ | ✔ |
| Eval[A] | ✔ | ✔ | ✔ | ✔ | ✗ | ✗ | ✗ | ✔ | ✔ |
| Option[A] | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✗ |
| Const[K, A] | ✔ | ✔ (`K:Monoid`) | ✔ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Either[E, A] | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✗ | ✗ |
| List[A] | ✔ | ✔ | ✔ | ✔ | ✔ | ✗ | ✗ | ✔ | ✗ |
| NonEmptyList[A] | ✔ | ✔ | ✔ | ✔ | ✗ | ✗ | ✗ | ✔ | ✔ |
| Stream[A] | ✔ | ✔ | ✔ | ✔ | ✔ | ✗ | ✗ | ✔ | ✗ |
| Map[K, A] | ✔ | ✔ | ✗ | ✗ | ✔ | ✗ | ✗ | ✗ | ✗ |
| Validated[E, A] | ✔ | ✔ (`E: Semigroup`)| ✔ | ✗ | ✗ | ✔ (`E: Semigroup`)| ✗ | ✗ | ✗ |
| Reader[E, A] | ✔ | ✔ | ✔ | ✔ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Writer[E, A] | ✔ | ✔ (`E:Monoid`) | ✔ | ✔ | ✗ | ✗ | ✗ | ✔ | ✗ |
| Type | Functor | Apply | Applicative | Monad | MonoidK | ApplicativeError | MonadError | CoflatMap | Comonad | Bimonad |
| --------------- |:-------:|:-----------------:|:-----------:|:-----:|:-------:|:-----------------:|:----------:|:---------:|:-------:|:-------:|
| Id[A] | ✔ | ✔ | ✔ | ✔ | ✗ | ✗ | ✗ | ✔ | ✔ |✔ |
| Eval[A] | ✔ | ✔ | ✔ | ✔ | ✗ | ✗ | ✗ | ✔ | ✔ |✔ |
| Option[A] | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✗ |✗ |
| Const[K, A] | ✔ | ✔ (`K:Monoid`) | ✔ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |✗ |
| Either[E, A] | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✗ | ✗ |✗ |
| List[A] | ✔ | ✔ | ✔ | ✔ | ✔ | ✗ | ✗ | ✔ | ✗ |✗ |
| NonEmptyList[A] | ✔ | ✔ | ✔ | ✔ | ✗ | ✗ | ✗ | ✔ | ✔ |✔ |
| Stream[A] | ✔ | ✔ | ✔ | ✔ | ✔ | ✗ | ✗ | ✔ | ✗ |✗ |
| Map[K, A] | ✔ | ✔ | ✗ | ✗ | ✔ | ✗ | ✗ | ✗ | ✗ |✗ |
| Validated[E, A] | ✔ | ✔ (`E: Semigroup`)| ✔ | ✗ | ✗ | ✔ (`E: Semigroup`)| ✗ | ✗ | ✗ |✗ |
| Reader[E, A] | ✔ | ✔ | ✔ | ✔ | ✗ | ✗ | ✗ | ✗ | ✗ |✗ |
| Writer[E, A] | ✔ | ✔ (`E:Monoid`) | ✔ | ✔ | ✗ | ✗ | ✗ | ✔ | ✗ |✗ |



Expand Down