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
38 changes: 18 additions & 20 deletions docs/src/main/mdoc/typeclasses/bimonad.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,41 +23,41 @@ def f[T[_] : Bimonad, S](fa: T[S]): S
keep in mind `Bimonad` has its own added laws so something that is both monadic
and comonadic may not necessarily be a lawful `Bimonad`.

###Eval as a Bimonad
Eval is a lawful `Bimonad` so you can chain computations (like a `Monad`) and `extract` the result at the end (like a `Comonad`).
### 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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Here is a possible implementation based on existing monad and comonad:
Here is a possible implementation:

```scala mdoc
import cats._
import cats.data._
import cats.implicits._

implicit def evalBimonad(implicit monad: Monad[Eval], comonad: Comonad[Eval]) =
new Bimonad[Eval] {
implicit def nelBimonad(implicit monad: Monad[NonEmptyList], comonad: Comonad[NonEmptyList]) =
new Bimonad[NonEmptyList] {

//use Eval specific methods for creation and extraction
override def pure[A](a: A): Eval[A] =
Eval.now(a)
//use NonEmptyList specific methods for creation and extraction
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a good place to point out the Bimonad law about the relationship between pure and extract? :)

Copy link
Contributor Author

@gatear gatear Dec 10, 2021

Choose a reason for hiding this comment

The 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](x: Eval[A]): A =
x.value
override def extract[A](fa: NonEmptyList[A]): A =
fa.head

//use the coflatMap from the Eval comonad
override def coflatMap[A, B](fa: Eval[A])(f: Eval[A] => B): Eval[B] =
//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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we can directly call flatMap and coflatMap directly on the NonEmptyList :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hopping this shows flatMap and coflatMap as originating from monad and comonad.
Don't you think it's a good idea to delegate to the monad and comonad variables ?

Copy link
Member

Choose a reason for hiding this comment

The 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 pure and extract are also defined in monad and comonad, so we should delegate those as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.
I thought using one and head in the implementation will make this more obvious:

nelBimonad.pure(true).extract === NonEmptyList.one(true).head

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought using one and head in the implementation will make this more obvious

I agree, I think this is the right way to do it. So, we can just delegate flatMap and coflatMap to the list without monad/comonad and maybe leave tailRecM as an exercise for the reader 😉

Anyway just my opinion, not really sure what the best thing to do here is. But I think separating the implementations of pure and extract makes it seem like they are inconsistent or different from the definitions in monad or comonad.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the current tailRecM implementation for NonEmptyList[_] doesn't look trivial .. I mean I fear Its understanding detours too much and I agree it's a good idea to leave it as an exercise 👍

Anyway just my opinion, not really sure what the best thing to do here is. But I think separating the implementations of pure and extract makes it seem like they are inconsistent or different from the definitions in monad or comonad.

Yes, I will remove monad and comonad.


//use the flatMap and tailRecM from the Eval monad
override def flatMap[A, B](fa: Eval[A])(f: A => Eval[B]): Eval[B] =
//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 => Eval[Either[A, B]]): Eval[B] =
override def tailRecM[A, B](a: A)(f: A => NonEmptyList[Either[A, B]]): NonEmptyList[B] =
monad.tailRecM(a)(f)
}
```

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

Using generic bimonad syntax we could define a function that appends and extracts a configuration:
Expand All @@ -70,14 +70,12 @@ def make[T[_]: Bimonad](config: T[String]): String =
.extract
```

This will work with all types of `Eval`:
This works with one element non-empty lists:
```scala mdoc
make(Eval.now("config"))

make(Eval.later("config"))
make(NonEmptyList.one("config"))
```

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

Expand Down