From 9adfc5eb92ddefc18881125c0f5842fc15079e34 Mon Sep 17 00:00:00 2001 From: gatear Date: Wed, 8 Dec 2021 17:00:24 +0200 Subject: [PATCH 01/10] Add `Bimonad` doc --- docs/src/main/mdoc/typeclasses/bimonad.md | 58 +++++++++++++++++++ docs/src/main/mdoc/typeclasses/typeclasses.md | 28 ++++----- 2 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 docs/src/main/mdoc/typeclasses/bimonad.md diff --git a/docs/src/main/mdoc/typeclasses/bimonad.md b/docs/src/main/mdoc/typeclasses/bimonad.md new file mode 100644 index 0000000000..a558f8c6de --- /dev/null +++ b/docs/src/main/mdoc/typeclasses/bimonad.md @@ -0,0 +1,58 @@ +--- +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 behaviour. `Bimonad` is +different of other `Bi` typeclasses like `Bifunctor`, `Bifoldable` or `Bitraverse` where the prefix describes a +`F[_, _]`. The `Bimonad` is a `F[_]` and could be better seen as a dual monad i.e. something that is both a `Monad` and +a `Comonad`. + +If you use `Bimonad` as a convenience type such that: +```scala mdoc:silent +def f[T[_] : Monad, Comonad, S](fa: T[S]): S +``` +is re-written to: +```scala mdoc:silent +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 and `extract` the result at the end. + +Note the equivalence: +```scala mdoc +Bimonad[Eval].pure(true).extract === Eval.now(true).value +``` + +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 will work with all types of `Eval`: +```scala mdoc +make(Eval.now("config")) +make(Eval.later("config")) + +//String = config with option A with option B with option C +``` + +Given that `Function0` or `NonEmptyList` are also lawful bimonads the following calls are also valid: +```scala mdoc +make(() => "config") +make(NonEmptyList.one("config")) + +//String = config with option A with option B with option C +``` \ No newline at end of file diff --git a/docs/src/main/mdoc/typeclasses/typeclasses.md b/docs/src/main/mdoc/typeclasses/typeclasses.md index cef20f7734..47b9ed60d8 100644 --- a/docs/src/main/mdoc/typeclasses/typeclasses.md +++ b/docs/src/main/mdoc/typeclasses/typeclasses.md @@ -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`) | ✔ | ✔ | ✗ | ✗ | ✗ | ✔ | ✗ |✗ | From 0a4ea096df3099d410c787373264cddb292d789d Mon Sep 17 00:00:00 2001 From: gatear Date: Wed, 8 Dec 2021 17:15:15 +0200 Subject: [PATCH 02/10] Fix typo in mdoc title --- docs/src/main/mdoc/typeclasses/bimonad.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/mdoc/typeclasses/bimonad.md b/docs/src/main/mdoc/typeclasses/bimonad.md index a558f8c6de..d6efce9ddc 100644 --- a/docs/src/main/mdoc/typeclasses/bimonad.md +++ b/docs/src/main/mdoc/typeclasses/bimonad.md @@ -1,6 +1,6 @@ --- layout: docs -title: "BiMonad" +title: "Bimonad" section: "typeclasses" source: "core/src/main/scala/cats/Bimonad.scala" scaladoc: "#cats.Bimonad" From 030405c23edc0f2a0e23dee00b021954b690f01b Mon Sep 17 00:00:00 2001 From: gatear Date: Thu, 9 Dec 2021 12:04:27 +0200 Subject: [PATCH 03/10] Add imports --- docs/src/main/mdoc/typeclasses/bimonad.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/docs/src/main/mdoc/typeclasses/bimonad.md b/docs/src/main/mdoc/typeclasses/bimonad.md index d6efce9ddc..5dc73b9c85 100644 --- a/docs/src/main/mdoc/typeclasses/bimonad.md +++ b/docs/src/main/mdoc/typeclasses/bimonad.md @@ -12,22 +12,19 @@ different of other `Bi` typeclasses like `Bifunctor`, `Bifoldable` or `Bitravers `F[_, _]`. The `Bimonad` is a `F[_]` and could be better seen as a dual monad i.e. something that is both a `Monad` and a `Comonad`. -If you use `Bimonad` as a convenience type such that: -```scala mdoc:silent -def f[T[_] : Monad, Comonad, S](fa: T[S]): S -``` -is re-written to: -```scala mdoc:silent -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`. +If you use `Bimonad` as a convenience type such that `def f[T[_] : Monad, Comonad, S](fa: T[S]): S` is re-written to +`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 and `extract` the result at the end. Note the equivalence: ```scala mdoc +import cats._ +import cats.data._ +import cats.implicits._ + Bimonad[Eval].pure(true).extract === Eval.now(true).value ``` From d4d97b2ce05bdbbbd2fde080458fb25420023d27 Mon Sep 17 00:00:00 2001 From: gatear Date: Thu, 9 Dec 2021 16:50:33 +0200 Subject: [PATCH 04/10] address comments from @armanbilge --- docs/src/main/mdoc/typeclasses/bimonad.md | 35 ++++++++++++++--------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/docs/src/main/mdoc/typeclasses/bimonad.md b/docs/src/main/mdoc/typeclasses/bimonad.md index 5dc73b9c85..97771b6459 100644 --- a/docs/src/main/mdoc/typeclasses/bimonad.md +++ b/docs/src/main/mdoc/typeclasses/bimonad.md @@ -7,17 +7,24 @@ scaladoc: "#cats.Bimonad" --- # Bimonad -The `Bimonad` trait directly extends `Monad` and `Comonad` without introducing new behaviour. `Bimonad` is -different of other `Bi` typeclasses like `Bifunctor`, `Bifoldable` or `Bitraverse` where the prefix describes a -`F[_, _]`. The `Bimonad` is a `F[_]` and could be better seen as a dual monad i.e. something that is both a `Monad` and -a `Comonad`. +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 `def f[T[_] : Monad, Comonad, S](fa: T[S]): S` is re-written to -`def f[T[_] : Bimonad, S](fa: T[S]): S` keep in mind `Bimonad` has its own added laws so something that is both monadic + +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 +``` +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 and `extract` the result at the end. +Eval is a lawful `Bimonad` so you can chain computations (like a `Monad`) and `extract` the result at the end (like a `Comonad`). Note the equivalence: ```scala mdoc @@ -25,7 +32,9 @@ import cats._ import cats.data._ import cats.implicits._ -Bimonad[Eval].pure(true).extract === Eval.now(true).value +val evalBimonad = Bimonad[Eval] + +evalBimonad.pure(true).extract === Eval.now(true).value ``` Using generic bimonad syntax we could define a function that appends and extracts a configuration: @@ -41,15 +50,13 @@ def make[T[_]: Bimonad](config: T[String]): String = This will work with all types of `Eval`: ```scala mdoc make(Eval.now("config")) -make(Eval.later("config")) -//String = config with option A with option B with option C +make(Eval.later("config")) ``` -Given that `Function0` or `NonEmptyList` are also lawful bimonads the following calls are also valid: +`Function0` and `NonEmptyList` are also lawful bimonads so the following calls are also valid: ```scala mdoc make(() => "config") -make(NonEmptyList.one("config")) -//String = config with option A with option B with option C -``` \ No newline at end of file +make(NonEmptyList.one("config")) +``` From 3f7180aef71f4dd75c33ec7244a64cfde9f60168 Mon Sep 17 00:00:00 2001 From: gatear Date: Thu, 9 Dec 2021 17:43:11 +0200 Subject: [PATCH 05/10] Define an Eval bimonad --- docs/src/main/mdoc/typeclasses/bimonad.md | 27 +++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/docs/src/main/mdoc/typeclasses/bimonad.md b/docs/src/main/mdoc/typeclasses/bimonad.md index 97771b6459..f265e6cecf 100644 --- a/docs/src/main/mdoc/typeclasses/bimonad.md +++ b/docs/src/main/mdoc/typeclasses/bimonad.md @@ -26,14 +26,37 @@ 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`). -Note the equivalence: +Here is a possible implementation based on existing monad and comonad: ```scala mdoc import cats._ import cats.data._ import cats.implicits._ -val evalBimonad = Bimonad[Eval] +implicit def evalBimonad(implicit monad: Monad[Eval], comonad: Comonad[Eval]) = + new Bimonad[Eval] { + + //use Eval specific methods for creation and extraction + override def pure[A](a: A): Eval[A] = + Eval.now(a) + + override def extract[A](x: Eval[A]): A = + x.value + + //use the coflatMap from the Eval comonad + override def coflatMap[A, B](fa: Eval[A])(f: Eval[A] => B): Eval[B] = + comonad.coflatMap(fa)(f) + + //use the flatMap and tailRecM from the Eval monad + override def flatMap[A, B](fa: Eval[A])(f: A => Eval[B]): Eval[B] = + monad.flatMap(fa)(f) + override def tailRecM[A, B](a: A)(f: A => Eval[Either[A, B]]): Eval[B] = + monad.tailRecM(a)(f) + } +``` + +Note the equivalence: +```scala mdoc evalBimonad.pure(true).extract === Eval.now(true).value ``` From 687dd0b6bbb024bcbddb57c9fe25002f2960fefa Mon Sep 17 00:00:00 2001 From: gatear Date: Thu, 9 Dec 2021 22:05:50 +0200 Subject: [PATCH 06/10] Swap `Eval[_]` with `NonEmptyList[_]` for the main example --- docs/src/main/mdoc/typeclasses/bimonad.md | 38 +++++++++++------------ 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/docs/src/main/mdoc/typeclasses/bimonad.md b/docs/src/main/mdoc/typeclasses/bimonad.md index f265e6cecf..acf8373233 100644 --- a/docs/src/main/mdoc/typeclasses/bimonad.md +++ b/docs/src/main/mdoc/typeclasses/bimonad.md @@ -23,8 +23,8 @@ 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: ```scala mdoc @@ -32,32 +32,32 @@ 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 + 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) - //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: @@ -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") From ea8322dc2c038726d1cb9cf6f6b16925677c2e1d Mon Sep 17 00:00:00 2001 From: gatear Date: Fri, 10 Dec 2021 13:32:39 +0200 Subject: [PATCH 07/10] Leave `tailRecM` implementation as an exercise and delegate to input NEL `flatMap` and `coflatMap` --- docs/src/main/mdoc/typeclasses/bimonad.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/src/main/mdoc/typeclasses/bimonad.md b/docs/src/main/mdoc/typeclasses/bimonad.md index acf8373233..db2586e59f 100644 --- a/docs/src/main/mdoc/typeclasses/bimonad.md +++ b/docs/src/main/mdoc/typeclasses/bimonad.md @@ -24,7 +24,7 @@ keep in mind `Bimonad` has its own added laws so something that is both monadic and comonadic may not necessarily be a lawful `Bimonad`. ### 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`). +`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: ```scala mdoc @@ -32,7 +32,7 @@ import cats._ import cats.data._ import cats.implicits._ -implicit def nelBimonad(implicit monad: Monad[NonEmptyList], comonad: Comonad[NonEmptyList]) = +implicit def nelBimonad = new Bimonad[NonEmptyList] { //use NonEmptyList specific methods for creation and extraction @@ -42,16 +42,18 @@ implicit def nelBimonad(implicit monad: Monad[NonEmptyList], comonad: Comonad[No override def extract[A](fa: NonEmptyList[A]): A = fa.head - //use the coflatMap from the NonEmptyList comonad + //use coflatMap from NonEmptyList override def coflatMap[A, B](fa: NonEmptyList[A])(f: NonEmptyList[A] => B): NonEmptyList[B] = - comonad.coflatMap(fa)(f) + fa.coflatMap(f) - //use the flatMap and tailRecM from the NonEmptyList monad + //use flatMap from NonEmptyList override def flatMap[A, B](fa: NonEmptyList[A])(f: A => NonEmptyList[B]): NonEmptyList[B] = - monad.flatMap(fa)(f) + fa.flatMap(f) - override def tailRecM[A, B](a: A)(f: A => NonEmptyList[Either[A, B]]): NonEmptyList[B] = - monad.tailRecM(a)(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] = + ??? } ``` @@ -79,5 +81,5 @@ make(NonEmptyList.one("config")) ```scala mdoc make(() => "config") -make(NonEmptyList.one("config")) +make(Eval.later("config")) ``` From 0ab5e3fdac1bc4dc5c1764784cec7ea016f6345e Mon Sep 17 00:00:00 2001 From: gatear Date: Fri, 10 Dec 2021 17:34:09 +0200 Subject: [PATCH 08/10] Use first bimonad law in implementation comments --- docs/src/main/mdoc/typeclasses/bimonad.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/mdoc/typeclasses/bimonad.md b/docs/src/main/mdoc/typeclasses/bimonad.md index db2586e59f..2af6caece1 100644 --- a/docs/src/main/mdoc/typeclasses/bimonad.md +++ b/docs/src/main/mdoc/typeclasses/bimonad.md @@ -26,7 +26,7 @@ and comonadic may not necessarily be a lawful `Bimonad`. ### 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: +Here is a possible implementation: ```scala mdoc import cats._ import cats.data._ @@ -35,7 +35,7 @@ import cats.implicits._ implicit def nelBimonad = new Bimonad[NonEmptyList] { - //use NonEmptyList specific methods for creation and extraction + //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) From 2923299413af53e2e098bb81a8df0d3230d6b667 Mon Sep 17 00:00:00 2001 From: gatear Date: Mon, 13 Dec 2021 11:36:26 +0200 Subject: [PATCH 09/10] address comments from @armanbilge --- docs/src/main/mdoc/typeclasses/bimonad.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/src/main/mdoc/typeclasses/bimonad.md b/docs/src/main/mdoc/typeclasses/bimonad.md index 2af6caece1..c1c8314b01 100644 --- a/docs/src/main/mdoc/typeclasses/bimonad.md +++ b/docs/src/main/mdoc/typeclasses/bimonad.md @@ -7,10 +7,11 @@ 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`. - +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 @@ -20,8 +21,7 @@ is re-written to: ```scala 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`. +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`). @@ -35,23 +35,23 @@ import cats.implicits._ implicit def nelBimonad = new Bimonad[NonEmptyList] { - //in order to have a lawful bimonad `pure` and `extract` need to respect: `nelBimonad.extract(nelBimonad.pure(a)) <-> a` + // 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 + // 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 + // 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 + // 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] = ??? } From 896b945ac13a65c472922650778fbd858ed7aa41 Mon Sep 17 00:00:00 2001 From: gatear Date: Fri, 24 Dec 2021 13:34:15 +0200 Subject: [PATCH 10/10] address comments from @johnynek --- docs/src/main/mdoc/typeclasses/bimonad.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/mdoc/typeclasses/bimonad.md b/docs/src/main/mdoc/typeclasses/bimonad.md index c1c8314b01..074b71c181 100644 --- a/docs/src/main/mdoc/typeclasses/bimonad.md +++ b/docs/src/main/mdoc/typeclasses/bimonad.md @@ -15,11 +15,11 @@ 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 +def f[T[_]: Monad: Comonad, S](fa: T[S]): S ``` is re-written to: ```scala -def f[T[_] : Bimonad, S](fa: T[S]): S +def f[T[_]: Bimonad, S](fa: T[S]): S ``` then `T[_]` also needs to respect an extra set of laws. @@ -32,7 +32,7 @@ import cats._ import cats.data._ import cats.implicits._ -implicit def nelBimonad = +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`