From 37b7fa7c51862b550a61be324f1d8398140ebb9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Koz=C5=82owski?= Date: Sun, 3 Jun 2018 21:55:27 +0200 Subject: [PATCH 1/3] Add an explanation for the missing Applicative instances for monad transformers --- docs/src/main/tut/faq.md | 7 +++++++ docs/src/main/tut/guidelines.md | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/docs/src/main/tut/faq.md b/docs/src/main/tut/faq.md index a886aecde1..408c9d9a68 100644 --- a/docs/src/main/tut/faq.md +++ b/docs/src/main/tut/faq.md @@ -17,6 +17,7 @@ position: 40 * [Why is some example code not compiling for me?](#example-compile) * [How can I turn my List of `` into a `` of a list?](#traverse) * [Where is `ListT`?](#listt) + * [Where are `Applicative`s for monad transformers?(#applicative-monad-transformers) * [Where is `IO`/`Task`?](#task) * [What does `@typeclass` mean?](#simulacrum) * [What do types like `?` and `λ` mean?](#kind-projector) @@ -124,6 +125,12 @@ def even(i: Int): ErrorsOr[Int] = if (i % 2 == 0) i.validNel else s"$i is odd".i nl.traverse(even) ``` +## Where are `Applicative`s for monad transformers? + +An `Applicative` instance for `OptionT[F, ?]`/`EitherT[F, E, ?]`, built without a corresponding `Monad` instance for `F`, would be unlawful, so it's not included. See [https://typelevel.org/cats/guidelines.html#applicative-monad-transformers](the guidelines) for a more detailed explanation. + +As an alternative, using `.toNested` on the monad transformer is recommended, although its `ap` will still be inconsistent with the Monad instance's.`. + ## Where is IO/Task? In purely functional programming, a monadic `IO` or `Task` type is often used to handle side effects such as file/network IO. In some languages and frameworks, such a type also serves as the primary abstraction through which parallelism is achieved. Nearly every real-world purely functional application or service is going to require such a data type, and this gives rise to an obvious question: why doesn't cats include such a type? diff --git a/docs/src/main/tut/guidelines.md b/docs/src/main/tut/guidelines.md index 2ebad93c81..3682be22da 100644 --- a/docs/src/main/tut/guidelines.md +++ b/docs/src/main/tut/guidelines.md @@ -116,6 +116,12 @@ abstract class KleisliInstance1 { We can introduce new type classes for the sake of adding laws that don't apply to the parent type class, e.g. `CommutativeSemigroup` and `CommutativeArrow`. +### Applicative instances for monad transformers + +We explicitly don't provide an instance of `Applicative` for e.g. `OptionT[F, ?]` given an `Applicative[F]`. +An attempt to construct one without a proper `Monad[F]` instance would be inconsistent in `ap` with the provided `Monad` instance +for `OptionT[F, ?]`. See [#1467](https://github.com/typelevel/cats/issues/1467) for the discussion. + #### TODO: Once we drop 2.10 support, AnyVal-extending class constructor parameters can be marked as private. From 67ca7b93a69f89c5f1f3d93546522cb6db4a68d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Koz=C5=82owski?= Date: Tue, 18 Dec 2018 16:08:58 +0100 Subject: [PATCH 2/3] Add example of unlawful Applicative for Nested/EitherT --- docs/src/main/tut/guidelines.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/src/main/tut/guidelines.md b/docs/src/main/tut/guidelines.md index 3682be22da..58a1ee88c5 100644 --- a/docs/src/main/tut/guidelines.md +++ b/docs/src/main/tut/guidelines.md @@ -88,19 +88,19 @@ This rule is relatively flexible. Use what you see appropriate. The goal is to m ### Implicit instance priority -When there are multiple instances provided implicitly, if the type class of them are in the same inheritance hierarchy, +When there are multiple instances provided implicitly, if the type class of them are in the same inheritance hierarchy, the instances need to be separated out into different abstract class/traits so that they don't conflict with each other. The names of these abstract classes/traits should be numbered with a priority with 0 being the highest priority. The abstract classes/trait -with higher priority inherits from the ones with lower priority. The most specific (whose type class is the lowest in the hierarchy) instance should be placed in the abstract class/ trait with the highest priority. Here is an example. +with higher priority inherits from the ones with lower priority. The most specific (whose type class is the lowest in the hierarchy) instance should be placed in the abstract class/ trait with the highest priority. Here is an example. ```scala -@typeclass +@typeclass trait Functor[F[_]] @typeclass trait Monad[F[_]] extends Functor ... -object Kleisli extends KleisliInstance0 +object Kleisli extends KleisliInstance0 abstract class KleisliInstance0 extends KleisliInstance1 { implicit def catsDataMonadForKleisli[F[_], A]: Monad[Kleisli[F, A, ?]] = ... @@ -113,14 +113,28 @@ abstract class KleisliInstance1 { ### Type classes that ONLY define laws. -We can introduce new type classes for the sake of adding laws that don't apply to the parent type class, e.g. `CommutativeSemigroup` and +We can introduce new type classes for the sake of adding laws that don't apply to the parent type class, e.g. `CommutativeSemigroup` and `CommutativeArrow`. ### Applicative instances for monad transformers -We explicitly don't provide an instance of `Applicative` for e.g. `OptionT[F, ?]` given an `Applicative[F]`. +We explicitly don't provide an instance of `Applicative` for e.g. `EitherT[F, String, ?]` given an `Applicative[F]`. An attempt to construct one without a proper `Monad[F]` instance would be inconsistent in `ap` with the provided `Monad` instance -for `OptionT[F, ?]`. See [#1467](https://github.com/typelevel/cats/issues/1467) for the discussion. +for `EitherT[F, String, ?]`. Such an instance will be derived if you use `Nested` instead: + +```scala +import cats._, cats.data._, cats.implicits._ + +val a = EitherT(List(Left("err"), Right(1))) +val x = (a *> a).value +x: List[Either[String, Int]] = List(Left("err"), Left("err"), Right(1)) + +val y = (a.toNested *> a.toNested).value +> y: List[Either[String, Int]] = List(Left("err"), Left("err"), Left("err"), Right(1)) + +x === y +> false +``` #### TODO: From 3b8dd0e739c223db3398e7db48227b7cb0909271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Koz=C5=82owski?= Date: Tue, 18 Dec 2018 16:16:12 +0100 Subject: [PATCH 3/3] Fix these --- docs/src/main/tut/guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/tut/guidelines.md b/docs/src/main/tut/guidelines.md index 58a1ee88c5..f6589d45f5 100644 --- a/docs/src/main/tut/guidelines.md +++ b/docs/src/main/tut/guidelines.md @@ -127,7 +127,7 @@ import cats._, cats.data._, cats.implicits._ val a = EitherT(List(Left("err"), Right(1))) val x = (a *> a).value -x: List[Either[String, Int]] = List(Left("err"), Left("err"), Right(1)) +> x: List[Either[String, Int]] = List(Left("err"), Left("err"), Right(1)) val y = (a.toNested *> a.toNested).value > y: List[Either[String, Int]] = List(Left("err"), Left("err"), Left("err"), Right(1))