Skip to content

Commit 171a09a

Browse files
author
Arnaud ESTEVE
committed
Step2: rewrite the Functor part in more details
1 parent 1bd517e commit 171a09a

File tree

1 file changed

+63
-2
lines changed

1 file changed

+63
-2
lines changed

docs/docs/reference/contextual/typeclasses-new.md

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,74 @@ or:
7777
assert(3 == List(1, 2).combineAll)
7878
```
7979

80-
### Functors and monads:
80+
### Functors:
81+
82+
A `Functor` represents the ability for a type containing zero or more elements, and that can be "mapped over": applying a function to every of its elements.
83+
Let's name our "type containing zero or more elements" `F`. It's a [higher-kinded type](http://guillaume.martres.me/publications/dotty-hk.pdf), since it contains elements of an other type.
84+
Therefore we'll write it `F[_]` since we don't really care about the type of the elements it contains.
85+
The definition of the `Functor` ability would thus be written as:
86+
87+
```scala
88+
trait Functor[F[_]] {
89+
def map[A, B](original: F[A], mapper: A => B): F[B]
90+
}
91+
```
92+
93+
Which could read as follows: "The `Functor` ability for a wrapper type `F` represents the ability to transform `F[A]` to `F[B]` through the application of the `mapper` function whose type is `A => B`".
94+
This way, we could define an instance of `Functor` for the `List` type:
95+
96+
```scala
97+
given as Functor[List] {
98+
def map[A, B](original: List[A], mapper: A => B): List[B] =
99+
original.map(mapper) // List already has a `map` method
100+
}
101+
```
102+
103+
With this `given` instance in scope, everywhere a `Functor` is expected, the compiler will accept a `List` to be used.
104+
105+
For instance, we may write such a testing method:
106+
```scala
107+
def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit =
108+
assert(expected == summon[Functor[F]].map(original, mapping))
109+
```
110+
111+
And use it this way, for example:
112+
113+
```scala
114+
assertTransformation(List("a1", "b1"), List("a", "b"), elt => s"${elt}1")
115+
```
116+
117+
That's a first step, but in practice we probably would like the `map` function to be a method directly accessible on the type `F`. So that we can call `map` directly on instances of `F`, and get rid of the `summon[Functor[F]]` part.
118+
As in the previous example of Monoids, [`extension` methods](extension-methods-new.html) help achieving that. Let's re-define the `Functor` _typeclass_ with extension methods.
81119

82120
```scala
83121
trait Functor[F[_]] {
84-
def [A, B](x: F[A]).map(f: A => B): F[B]
122+
def [A, B](original: F[A]).map(mapper: A => B): F[B]
85123
}
124+
```
125+
126+
The instance of `Functor` for `List` now becomes:
127+
128+
```scala
129+
given as Functor[List] {
130+
def [A, B](original: List[A]).map(mapper: A => B): List[B] =
131+
original.map(mapper) // List already has a `map` method
132+
}
133+
```
86134

135+
It simplifies the `assertTransformation` method:
136+
137+
```scala
138+
def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit =
139+
assert(expected == original.map(mapping))
140+
```
141+
142+
Since we can now use the `map` method directly accessible on `original` which is of type `F[A]`, where `F` is a `Functor`.
143+
144+
145+
### Monads
146+
147+
```scala
87148
trait Monad[F[_]] extends Functor[F] {
88149
def [A, B](x: F[A]).flatMap(f: A => F[B]): F[B]
89150
def [A, B](x: F[A]).map(f: A => B) = x.flatMap(f `andThen` pure)

0 commit comments

Comments
 (0)