Skip to content

Commit 9209bbe

Browse files
authored
Merge pull request #1513 from adelbertc/doc-functors
Revamp Functor, Applicative, Traverse doc
2 parents 2e621b8 + 636d6df commit 9209bbe

6 files changed

Lines changed: 490 additions & 471 deletions

File tree

docs/src/main/resources/microsite/data/menu.yml

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
options:
22

3-
#############################
3+
#############################
44
# Type Classes Menu Options #
55
#############################
66

@@ -13,7 +13,7 @@ options:
1313
url: typeclasses/semigroup.html
1414
menu_type: typeclasses
1515
menu_section: semigroupsandmonoids
16-
16+
1717
nested_options:
1818
- title: Semigroup
1919
url: typeclasses/semigroup.html
@@ -23,30 +23,27 @@ options:
2323
menu_section: semigroupsandmonoids
2424

2525
- title: Applicative and Traversable Functors
26-
url: typeclasses/functor.html
26+
url: typeclasses/applicativetraverse.html
2727
menu_type: typeclasses
28-
menu_section: aplicative
29-
28+
menu_section: applicative
29+
3030
nested_options:
3131
- title: Functor
3232
url: typeclasses/functor.html
33-
menu_section: aplicative
34-
- title: Apply
35-
url: typeclasses/apply.html
36-
menu_section: aplicative
33+
menu_section: applicative
3734
- title: Applicative
3835
url: typeclasses/applicative.html
39-
menu_section: aplicative
36+
menu_section: applicative
4037
- title: Traverse
4138
url: typeclasses/traverse.html
4239
menu_type: typeclasses
43-
menu_section: aplicative
40+
menu_section: applicative
4441

4542
- title: Monads
4643
url: typeclasses/functor.html
4744
menu_type: typeclasses
4845
menu_section: monads
49-
46+
5047
nested_options:
5148
- title: Functor
5249
url: typeclasses/functor.html
@@ -66,7 +63,7 @@ options:
6663
url: typeclasses/functor.html
6764
menu_type: typeclasses
6865
menu_section: variance
69-
66+
7067
nested_options:
7168
- title: Functor
7269
url: typeclasses/functor.html
@@ -102,7 +99,7 @@ options:
10299
url: typeclasses/show.html
103100
menu_type: typeclasses
104101

105-
###########################
102+
###########################
106103
# Data Types Menu Options #
107104
###########################
108105

@@ -144,4 +141,4 @@ options:
144141

145142
- title: Validated
146143
url: datatypes/validated.html
147-
menu_type: data
144+
menu_type: data

docs/src/main/tut/typeclasses/applicative.md

Lines changed: 259 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,277 @@ source: "core/src/main/scala/cats/Applicative.scala"
66
scaladoc: "#cats.Applicative"
77
---
88
# Applicative
9+
`Applicative` extends [`Functor`](functor.html) with an `ap` and `pure` method.
910

10-
`Applicative` extends [`Apply`](apply.html) by adding a single method,
11-
`pure`:
11+
```tut:book:silent
12+
import cats.Functor
1213
13-
```scala
14-
def pure[A](x: A): F[A]
15-
````
14+
trait Applicative[F[_]] extends Functor[F] {
15+
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
16+
17+
def pure[A](a: A): F[A]
18+
19+
def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa)
20+
}
21+
```
22+
23+
`pure` wraps the value into the type constructor - for `Option` this could be `Some(_)`, for `Future`
24+
`Future.successful`, and for `List` a singleton list.
25+
26+
`ap` is a bit tricky to explain and motivate, so we'll look at an alternative but equivalent
27+
formulation via `product`.
28+
29+
```tut:book:silent
30+
trait Applicative[F[_]] extends Functor[F] {
31+
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
32+
33+
def pure[A](a: A): F[A]
34+
}
35+
36+
// Example implementation for right-biased Either
37+
implicit def applicativeForEither[L]: Applicative[Either[L, ?]] = new Applicative[Either[L, ?]] {
38+
def product[A, B](fa: Either[L, A], fb: Either[L, B]): Either[L, (A, B)] = (fa, fb) match {
39+
case (Right(a), Right(b)) => Right((a, b))
40+
case (Left(l) , _ ) => Left(l)
41+
case (_ , Left(l) ) => Left(l)
42+
}
43+
44+
def pure[A](a: A): Either[L, A] = Right(a)
45+
46+
def map[A, B](fa: Either[L, A])(f: A => B): Either[L, B] = fa match {
47+
case Right(a) => Right(f(a))
48+
case Left(l) => Left(l)
49+
}
50+
}
51+
```
52+
53+
Note that in this formulation `map` is left abstract, whereas in the previous one with `ap` `map`
54+
could be implemented in terms of `ap` and `pure`. This suggests that `ap` is equivalent to
55+
`map` and `product`, which is indeed the case.
56+
57+
Such an `Applicative` must obey three laws:
58+
59+
* Associativity: No matter the order in which you product together three values, the result is isomorphic
60+
* `fa.product(fb).product(fc) ~ fa.product(fb.product(fc))`
61+
* With `map`, this can be made into an equality with `fa.product(fb).product(fc) = fa.product(fb.product(fc)).map { case (a, (b, c)) => ((a, b), c) }`
62+
* Left identity: Zipping a value on the left with unit results in something isomorphic to the original value
63+
* `pure(()).product(fa) ~ fa`
64+
* As an equality: `pure(()).product(fa).map(_._2) = fa`
65+
* Right identity: Zipping a value on the right with unit results in something isomorphic to the original value
66+
* `fa.product(pure(())) ~ fa`
67+
* As an equality: `fa.product(pure(())).map(_._1) = fa`
68+
69+
## Applicatives for effect management
70+
71+
If we view `Functor` as the ability to work with a single effect, `Applicative` encodes working with
72+
multiple **independent** effects. Between `product` and `map`, we can take two separate effectful values
73+
and compose them. From there we can generalize to working with any N number of independent effects.
74+
75+
```tut:reset:book:silent
76+
import cats.Applicative
77+
78+
def product3[F[_]: Applicative, A, B, C](fa: F[A], fb: F[B], fc: F[C]): F[(A, B, C)] = {
79+
val F = Applicative[F]
80+
val fabc = F.product(F.product(fa, fb), fc)
81+
F.map(fabc) { case ((a, b), c) => (a, b, c) }
82+
}
83+
```
84+
85+
## What is ap?
86+
87+
Let's see what happens if we try to compose two effectful values with just `map`.
88+
89+
```tut:book:silent
90+
import cats.instances.option._
91+
92+
val f: (Int, Char) => Double = (i, c) => (i + c).toDouble
93+
94+
val int: Option[Int] = Some(5)
95+
val char: Option[Char] = Some('a')
96+
```
97+
98+
```tut:book
99+
int.map(i => (c: Char) => f(i, c)) // what now?
100+
```
101+
102+
We have an `Option[Char => Double]` and an `Option[Double]` to which we want to apply the function to,
103+
but `map` doesn't give us enough power to do that. Hence, `ap`.
104+
105+
## Applicatives compose
106+
107+
Like [`Functor`](functor.html), `Applicative`s compose. If `F` and `G` have `Applicative` instances, then so
108+
does `F[G[_]]`.
109+
110+
```tut:book:silent
111+
import cats.data.Nested
112+
import cats.instances.future._
113+
import scala.concurrent.Future
114+
import scala.concurrent.ExecutionContext.Implicits.global
115+
116+
val x: Future[Option[Int]] = Future.successful(Some(5))
117+
val y: Future[Option[Char]] = Future.successful(Some('a'))
118+
```
119+
120+
```tut:book
121+
val composed = Applicative[Future].compose[Option].map2(x, y)(_ + _)
122+
123+
val nested = Applicative[Nested[Future, Option, ?]].map2(Nested(x), Nested(y))(_ + _)
124+
```
125+
126+
## Traverse
127+
128+
The straightforward way to use `product` and `map` (or just `ap`) is to compose `n` independent effects,
129+
where `n` is a fixed number. In fact there are convenience methods named `apN`, `mapN`, and `tupleN` (replacing
130+
`N` with a number 2 - 22) to make it even easier.
131+
132+
Imagine we have one `Option` representing a username, one representing a password, and another representing
133+
a URL for logging into a database.
134+
135+
```tut:book:silent
136+
import java.sql.Connection
137+
138+
val username: Option[String] = Some("username")
139+
val password: Option[String] = Some("password")
140+
val url: Option[String] = Some("some.login.url.here")
141+
142+
// Stub for demonstration purposes
143+
def attemptConnect(username: String, password: String, url: String): Option[Connection] = None
144+
```
16145

17-
This method takes any value and returns the value in the context of
18-
the functor. For many familiar functors, how to do this is
19-
obvious. For `Option`, the `pure` operation wraps the value in
20-
`Some`. For `List`, the `pure` operation returns a single element
21-
`List`:
146+
We know statically we have 3 `Option`s, so we can use `map3` specifically.
22147

23148
```tut:book
24-
import cats._
149+
Applicative[Option].map3(username, password, url)(attemptConnect)
150+
```
151+
152+
Sometimes we don't know how many effects will be in play - perhaps we are receiving a list from user
153+
input or getting rows from a database. This implies the need for a function:
154+
155+
```tut:book:silent
156+
def sequenceOption[A](fa: List[Option[A]]): Option[List[A]] = ???
157+
158+
// Alternatively..
159+
def traverseOption[A, B](as: List[A])(f: A => Option[B]): Option[List[B]] = ???
160+
```
161+
162+
Users of the standard library `Future.sequence` or `Future.traverse` will find these names and signatures
163+
familiar.
164+
165+
Let's implement `traverseOption` (you can implement `sequenceOption` in terms of `traverseOption`).
166+
167+
```tut:book:silent
168+
def traverseOption[A, B](as: List[A])(f: A => Option[B]): Option[List[B]] =
169+
as.foldRight(Some(List.empty[B]): Option[List[B]]) { (a: A, acc: Option[List[B]]) =>
170+
val optB: Option[B] = f(a)
171+
// optB and acc are independent effects so we can use Applicative to compose
172+
Applicative[Option].map2(optB, acc)(_ :: _)
173+
}
174+
```
175+
176+
```tut:book
177+
traverseOption(List(1, 2, 3))(i => Some(i): Option[Int])
178+
```
179+
180+
This works...but if we look carefully at the implementation there's nothing `Option`-specific going on. As
181+
another example let's implement the same function but for `Either`.
182+
183+
```tut:book:silent
184+
import cats.instances.either._
185+
186+
def traverseEither[E, A, B](as: List[A])(f: A => Either[E, B]): Either[E, List[B]] =
187+
as.foldRight(Right(List.empty[B]): Either[E, List[B]]) { (a: A, acc: Either[E, List[B]]) =>
188+
val eitherB: Either[E, B] = f(a)
189+
Applicative[Either[E, ?]].map2(eitherB, acc)(_ :: _)
190+
}
191+
```
192+
193+
```tut:book
194+
traverseEither(List(1, 2, 3))(i => if (i % 2 != 0) Left(s"${i} is not even") else Right(i / 2))
195+
```
196+
197+
The implementation of `traverseOption` and `traverseEither` are more or less identical, modulo the initial
198+
"accumulator" to `foldRight`. But even that could be made the same by delegating to `Applicative#pure`!
199+
Generalizing `Option` and `Either` to any `F[_]: Applicative` gives us the fully polymorphic version.
200+
Existing data types with `Applicative` instances (`Future`, `Option`, `Either[E, ?]`, `Try`) can call it by fixing `F`
201+
appropriately, and new data types need only be concerned with implementing `Applicative` to do so as well.
202+
203+
```tut:book:silent
204+
def traverse[F[_]: Applicative, A, B](as: List[A])(f: A => F[B]): F[List[B]] =
205+
as.foldRight(Applicative[F].pure(List.empty[B])) { (a: A, acc: F[List[B]]) =>
206+
val fb: F[B] = f(a)
207+
Applicative[F].map2(fb, acc)(_ :: _)
208+
}
209+
```
210+
211+
This function is provided by Cats via the `Traverse[List]` instance and syntax, which is covered in another
212+
tutorial.
213+
214+
```tut:book:silent
215+
import cats.instances.list._
216+
import cats.syntax.traverse._
217+
```
218+
219+
```tut:book
220+
List(1, 2, 3).traverse(i => Some(i): Option[Int])
221+
```
222+
223+
With this addition of `traverse`, we can now compose any number of independent effects, statically known or otherwise.
224+
225+
## Apply - a weakened Applicative
226+
A closely related type class is `Apply` which is identical to `Applicative`, modulo the `pure`
227+
method. Indeed in Cats `Applicative` is a subclass of `Apply` with the addition of this method.
228+
229+
```scala
230+
trait Apply[F[_]] extends Functor[F] {
231+
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
232+
}
233+
234+
trait Applicative[F[_]] extends Apply[F] {
235+
def pure[A](a: A): F[A]
236+
237+
def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa)
238+
}
239+
```
240+
241+
The laws for `Apply` are just the laws of `Applicative` that don't mention `pure`. In the laws given
242+
above, the only law would be associativity.
243+
244+
One of the motivations for `Apply`'s existence is that some types have `Apply` instances but not
245+
`Applicative` - one example is `Map[K, ?]`. Consider the behavior of `pure` for `Map[K, A]`. Given
246+
a value of type `A`, we need to associate some arbitrary `K` to it but we have no way of doing that.
247+
248+
However, given existing `Map[K, A]` and `Map[K, B]` (or `Map[K, A => B]`), it is straightforward to
249+
pair up (or apply functions to) values with the same key. Hence `Map[K, ?]` has an `Apply` instance.
250+
251+
## Syntax
252+
253+
Syntax for `Applicative` (or `Apply`) is available under the `cats.implicits._` import. The most
254+
interesting syntax is focused on composing independent effects - there are two options for this.
255+
256+
The first is the builder syntax which incrementally builds up a collection of effects until a
257+
function is applied to compose them.
258+
259+
```tut:book:silent
25260
import cats.implicits._
26261
27-
Applicative[Option].pure(1)
28-
Applicative[List].pure(1)
262+
val o1: Option[Int] = Some(42)
263+
val o2: Option[String] = Some("hello")
29264
```
30265

31-
Like [`Functor`](functor.html) and [`Apply`](apply.html), `Applicative`
32-
functors also compose naturally with each other. When
33-
you compose one `Applicative` with another, the resulting `pure`
34-
operation will lift the passed value into one context, and the result
35-
into the other context:
266+
```tut:book
267+
(o1 |@| o2).map((i: Int, s: String) => i.toString ++ s)
268+
(o1 |@| o2).tupled
269+
```
270+
271+
The second expects the effects in a tuple and works by enriching syntax on top of the existing
272+
`TupleN` types.
36273

37274
```tut:book
38-
import cats.data.Nested
39-
val nested = Applicative[Nested[List, Option, ?]].pure(1)
40-
val unwrapped = nested.value
275+
(o1, o2).map2((i: Int, s: String) => i.toString ++ s)
41276
```
42277

43-
## Applicative Functors & Monads
278+
## Further Reading
44279

45-
`Applicative` is a generalization of [`Monad`](monad.html), allowing expression
46-
of effectful computations in a pure functional way.
280+
* [Applicative Programming with Effects][applicativePaper] - McBride, Patterson. JFP 2008.
47281

48-
`Applicative` is generally preferred to `Monad` when the structure of a
49-
computation is fixed a priori. That makes it possible to perform certain
50-
kinds of static analysis on applicative values.
282+
[applicativePaper]: http://www.staff.city.ac.uk/~ross/papers/Applicative.html "Applicative Programming with Effects"

0 commit comments

Comments
 (0)