-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Changes from 7 commits
9adfc5e
5d0d8ef
0a4ea09
030405c
d4d97b2
3f7180a
687dd0b
ea8322d
0ab5e3f
2923299
896b945
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,83 @@ | ||||||
--- | ||||||
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`. | ||||||
|
||||||
|
||||||
If you use `Bimonad` as a convenience type such that: | ||||||
```scala | ||||||
def f[T[_] : Monad, Comonad, S](fa: T[S]): S | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be I think the comma here is making There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep my bad .. |
||||||
``` | ||||||
is re-written to: | ||||||
```scala | ||||||
def f[T[_] : Bimonad, S](fa: T[S]): S | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: we generally don't put a space after the type, so we would change to: |
||||||
``` | ||||||
keep in mind `Bimonad` has its own added laws so something that is both monadic | ||||||
and comonadic may not necessarily be a lawful `Bimonad`. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is important enough to include at the beginning ... |
||||||
|
||||||
### 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 based on existing monad and comonad: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
```scala mdoc | ||||||
import cats._ | ||||||
import cats.data._ | ||||||
import cats.implicits._ | ||||||
|
||||||
implicit def nelBimonad(implicit monad: Monad[NonEmptyList], comonad: Comonad[NonEmptyList]) = | ||||||
new Bimonad[NonEmptyList] { | ||||||
|
||||||
//use NonEmptyList specific methods for creation and extraction | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe a good place to point out the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. //in order to have a lawful bimonad `pure` and `extract` need to respect: `nelBimonad.extract(nelBimonad.pure(a)) <-> a` Something like this ? |
||||||
override def pure[A](a: A): NonEmptyList[A] = | ||||||
NonEmptyList.one(a) | ||||||
|
||||||
override def extract[A](fa: NonEmptyList[A]): A = | ||||||
fa.head | ||||||
|
||||||
//use the coflatMap from the NonEmptyList comonad | ||||||
override def coflatMap[A, B](fa: NonEmptyList[A])(f: NonEmptyList[A] => B): NonEmptyList[B] = | ||||||
comonad.coflatMap(fa)(f) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually we can directly call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was hopping this shows There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I do see that now. But then isn't it confusing, because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. nelBimonad.pure(true).extract === NonEmptyList.one(true).head There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I agree, I think this is the right way to do it. So, we can just delegate Anyway just my opinion, not really sure what the best thing to do here is. But I think separating the implementations of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the current
Yes, I will remove |
||||||
|
||||||
//use the flatMap and tailRecM from the NonEmptyList monad | ||||||
override def flatMap[A, B](fa: NonEmptyList[A])(f: A => NonEmptyList[B]): NonEmptyList[B] = | ||||||
monad.flatMap(fa)(f) | ||||||
|
||||||
override def tailRecM[A, B](a: A)(f: A => NonEmptyList[Either[A, B]]): NonEmptyList[B] = | ||||||
monad.tailRecM(a)(f) | ||||||
} | ||||||
``` | ||||||
|
||||||
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(NonEmptyList.one("config")) | ||||||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... like this: