|
77 | 77 | assert(3 == List(1, 2).combineAll)
|
78 | 78 | ```
|
79 | 79 |
|
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. |
81 | 119 |
|
82 | 120 | ```scala
|
83 | 121 | 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] |
85 | 123 | }
|
| 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 | +``` |
86 | 134 |
|
| 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 |
87 | 148 | trait Monad[F[_]] extends Functor[F] {
|
88 | 149 | def [A, B](x: F[A]).flatMap(f: A => F[B]): F[B]
|
89 | 150 | def [A, B](x: F[A]).map(f: A => B) = x.flatMap(f `andThen` pure)
|
|
0 commit comments