Skip to content

Commit 9e3da24

Browse files
committed
Fix a typo and remove trailing : in the titles of type-classes.md
1 parent 4cf89cc commit 9e3da24

File tree

1 file changed

+16
-7
lines changed

1 file changed

+16
-7
lines changed

docs/docs/reference/contextual/type-classes.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ title: "Implementing Type classes"
44
---
55

66
A _type class_ is an abstract, parameterized type that lets you add new behavior to any closed data type without using sub-typing. This can be useful in multiple use-cases, for example:
7+
78
* expressing how a type you don't own (from the standard or 3rd-party library) conforms to such behavior
89
* expressing such a behavior for multiple types without involving sub-typing relationships (one `extends` another) between those types (see: [ad hoc polymorphism](https://en.wikipedia.org/wiki/Ad_hoc_polymorphism) for instance)
910

1011
Therefore in Scala 3, _type classes_ are just _traits_ with one or more parameters whose implementations are not defined through the `extends` keyword, but by **given instances**.
1112
Here are some examples of common type classes:
1213

13-
### Semigroups and monoids:
14+
### Semigroups and monoids
1415

1516
Here's the `Monoid` type class definition:
1617

@@ -31,6 +32,7 @@ given Monoid[String]:
3132
```
3233

3334
Whereas for the type `Int` one could write the following:
35+
3436
```scala
3537
given Monoid[Int]:
3638
extension (x: Int) def combine (y: Int): Int = x + y
@@ -58,7 +60,7 @@ def combineAll[T: Monoid](xs: List[T]): T =
5860
xs.foldLeft(Monoid[T].unit)(_.combine(_))
5961
```
6062

61-
### Functors:
63+
### Functors
6264

6365
A `Functor` for a type provides the ability for its values to be "mapped over", i.e. apply a function that transforms inside a value while remembering its shape. For example, to modify every element of a collection without dropping or adding elements.
6466
We can represent all types that can be "mapped over" with `F`. It's a type constructor: the type of its values becomes concrete when provided a type argument.
@@ -69,6 +71,7 @@ The definition of a generic `Functor` would thus be written as:
6971
trait Functor[F[_]]:
7072
def map[A, B](x: F[A], f: A => B): F[B]
7173
```
74+
7275
Which could read as follows: "A `Functor` for the type constructor `F[_]` represents the ability to transform `F[A]` to `F[B]` through the application of function `f` with type `A => B`". We call the `Functor` definition here a _type class_.
7376
This way, we could define an instance of `Functor` for the `List` type:
7477

@@ -81,6 +84,7 @@ given Functor[List]:
8184
With this `given` instance in scope, everywhere a `Functor` is expected, the compiler will accept a `List` to be used.
8285

8386
For instance, we may write such a testing method:
87+
8488
```scala
8589
def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit =
8690
assert(expected == summon[Functor[F]].map(original, mapping))
@@ -117,18 +121,18 @@ It simplifies the `assertTransformation` method:
117121
def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit =
118122
assert(expected == original.map(mapping))
119123
```
124+
120125
The `map` method is now directly used on `original`. It is available as an extension method
121126
since `original`'s type is `F[A]` and a given instance for `Functor[F[A]]` which defines `map`
122127
is in scope.
123128

124-
125129
### Monads
126130

127131
Applying `map` in `Functor[List]` to a mapping function of type `A => B` results in a `List[B]`. So applying it to a mapping function of type `A => List[B]` results in a `List[List[B]]`. To avoid managing lists of lists, we may want to "flatten" the values in a single list.
128132

129133
That's where `Monad` comes in. A `Monad` for type `F[_]` is a `Functor[F]` with two more operations:
130-
* `flatMap`, which turns an `F[A]` into an `F[B]` when given a function of type
131-
`A => F[B]`,
134+
135+
* `flatMap`, which turns an `F[A]` into an `F[B]` when given a function of type `A => F[B]`,
132136
* `pure`, which creates an `F[A]` from a single value `A`.
133137

134138
Here is the translation of this definition in Scala 3:
@@ -152,6 +156,7 @@ end Monad
152156
#### List
153157

154158
A `List` can be turned into a monad via this `given` instance:
159+
155160
```scala
156161
given listMonad as Monad[List]:
157162
def pure[A](x: A): List[A] =
@@ -160,6 +165,7 @@ given listMonad as Monad[List]:
160165
def flatMap(f: A => List[B]): List[B] =
161166
xs.flatMap(f) // rely on the existing `flatMap` method of `List`
162167
```
168+
163169
Since `Monad` is a subtype of `Functor`, `List` is also a functor. The Functor's `map`
164170
operation is already provided by the `Monad` trait, so the instance does not need to define
165171
it explicitly.
@@ -196,13 +202,17 @@ def layout(str: String)(config: Config): Unit = ???
196202
We may want to combine `compute` and `show` into a single function, accepting a `Config` as parameter, and showing the result of the computation, and we'd like to use
197203
a monad to avoid passing the parameter explicitly multiple times. So postulating
198204
the right `flatMap` operation, we could write:
205+
199206
```scala
200207
def computeAndShow(i: Int): Config => Unit = compute(i).flatMap(show)
201208
```
209+
202210
instead of
211+
203212
```scala
204213
show(compute(i)(config))(config)
205214
```
215+
206216
Let's define this m then. First, we are going to define a type named `ConfigDependent` representing a function that when passed a `Config` produces a `Result`.
207217

208218
```scala
@@ -232,7 +242,6 @@ type ConfigDependent = [Result] =>> Config => Result
232242

233243
Using this syntax would turn the previous `configDependentMonad` into:
234244

235-
236245
```scala
237246
given configDependentMonad as Monad[[Result] =>> Config => Result]
238247

@@ -266,7 +275,7 @@ end readerMonad
266275
The definition of a _type class_ is expressed with a parameterised type with abstract members, such as a `trait`.
267276
The main difference between subtype polymorphism and ad-hoc polymorphism with _type classes_ is how the definition of the _type class_ is implemented, in relation to the type it acts upon.
268277
In the case of a _type class_, its implementation for a concrete type is expressed through a `given` instance definition, which is supplied as an implicit argument alongside the value it acts upon. With subtype polymorphism, the implementation is mixed into the parents of a class, and only a single term is required to perform a polymorphic operation. The type class solution
269-
takes more effort to set up, but is more extensible: Adding a new interface to a class
278+
takes more effort to set up, but is more extensible: Adding a new interface to a
270279
class requires changing the source code of that class. But contrast, instances for type classes can be defined anywhere.
271280

272281
To conclude, we have seen that traits and given instances, combined with other constructs like extension methods, context bounds and type lambdas allow a concise and natural expression of _type classes_.

0 commit comments

Comments
 (0)