Skip to content

Commit

Permalink
Add an explanation for the missing Applicative instances for monad tr…
Browse files Browse the repository at this point in the history
…ansformers (#2277)

* Add an explanation for the missing Applicative instances for monad
transformers

* Add example of unlawful Applicative for Nested/EitherT

* Fix these
  • Loading branch information
kubukoz authored and kailuowang committed Dec 19, 2018
1 parent 342433c commit d117df8
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 5 deletions.
7 changes: 7 additions & 0 deletions docs/src/main/tut/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ position: 40
* [Why is some example code not compiling for me?](#example-compile)
* [How can I turn my List of `<something>` into a `<something>` 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)
Expand Down Expand Up @@ -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)
```

## <a id="applicative-monad-transformers" href="#applicative-monad-transformers">Where are `Applicative`s for monad transformers?</a>

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.`.

## <a id="task" href="#task"></a>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?
Expand Down
30 changes: 25 additions & 5 deletions docs/src/main/tut/guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,19 @@ This rule is relatively flexible. Use what you see appropriate. The goal is to m

### <a id="implicit-priority" href="#implicit-priority"></a> 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, ?]] = ...
Expand All @@ -113,9 +113,29 @@ 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`.

### <a id="applicative-monad-transformers" href="#applicative-monad-transformers">Applicative instances for monad transformers</a>

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 `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:

Once we drop 2.10 support, AnyVal-extending class constructor parameters can be marked as private.

0 comments on commit d117df8

Please sign in to comment.