Skip to content

Commit 8b015ee

Browse files
committed
Add "Exact mechanism" section to derivation.md
1 parent 5ada87f commit 8b015ee

File tree

1 file changed

+119
-6
lines changed

1 file changed

+119
-6
lines changed

Diff for: docs/_docs/reference/contextual/derivation.md

+119-6
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ nightlyOf: https://docs.scala-lang.org/scala3/reference/contextual/derivation.ht
55
---
66

77
Type class derivation is a way to automatically generate given instances for type classes which satisfy some simple
8-
conditions. A type class in this sense is any trait or class with a type parameter determining the type being operated
9-
on. Common examples are `Eq`, `Ordering`, or `Show`. For example, given the following `Tree` algebraic data type
8+
conditions. A type class in this sense is any trait or class with a single type parameter determining the type being operated
9+
on, and the special case `CanEqual`. Common examples are `Eq`, `Ordering`, or `Show`. For example, given the following `Tree` algebraic data type
1010
(ADT):
1111

1212
```scala
@@ -26,17 +26,130 @@ given [T: Show] : Show[Tree[T]] = Show.derived
2626

2727
We say that `Tree` is the _deriving type_ and that the `Eq`, `Ordering` and `Show` instances are _derived instances_.
2828

29-
**Note:** The access to `derived` above is a normal access, therefore if there are multiple definitions of `derived` in the type class, overloading resolution applies.
30-
31-
**Note:** `derived` can be used manually, this is useful when you do not have control over the definition. For example we can implement an `Ordering` for `Option`s like so:
29+
**Note:** `derived` can be used manually, this is useful when you do not have control over the definition. For example we can implement `Ordering` for `Option`s like so:
3230

3331
```scala
3432
given [T: Ordering]: Ordering[Option[T]] = Ordering.derived
3533
```
3634

3735
It is discouraged to directly refer to the `derived` member if you can use a `derives` clause instead.
3836

39-
All data types can have a `derives` clause. This document focuses primarily on data types which also have a given instance
37+
## Exact mechanism
38+
In the following, when type arguments are enumerated and the first index evaluates to a larger value than the last, then there are actually no arguments, for example: `A[T_2, ..., T_1]` means `A`.
39+
40+
For a class/trait/object/enum `DerivingType[T_1, ..., T_N] derives TC`, a derived instance is created in `DerivingType`'s companion object (or `DerivingType` itself if it is an object).
41+
42+
The general "shape" of the derived instance is as follows:
43+
```scala
44+
given [...](using ...): TC[ ... DerivingType[...] ... ] = TC.derived
45+
```
46+
`TC.derived` should be an expression that conforms to the expected type on the left, potentially elaborated using term and/or type inference.
47+
48+
**Note:** `TC.derived` is a normal access, therefore if there are multiple definitions of `TC.derived`, overloading resolution applies.
49+
50+
What the derived instance precisely looks like depends on the specifics of `DerivingType` and `TC`, we first examine `TC`:
51+
52+
### `TC` takes 1 parameter `F`
53+
54+
Therefore `TC` is defined as `TC[F[A_1, ..., A_K]]` (`TC[F]` if `K == 0`) for some `F`.
55+
There are two further cases depending on the kinds of arguments:
56+
57+
#### `F` and all arguments of `DerivingType` have kind `*`
58+
**Note:** `K == 0` in this case.
59+
60+
The generated instance is then:
61+
```scala
62+
given [T_1: TC, ..., T_N: TC]: TC[DerivingType[T_1, ..., T_N]] = TC.derived
63+
```
64+
65+
This is the most common case, and is the one that was highlighted in the introduction.
66+
67+
**Note:** The `[T_i: TC, ...]` introduces a `(using TC[T_i], ...)`, more information in [Context Bounds](./context-bounds.md).
68+
This allows the `derived` member to access these evidences.
69+
70+
**Note:** If `N == 0` the above means:
71+
```scala
72+
given TC[DerivingType] = TC.derived
73+
```
74+
For example, the class
75+
```scala
76+
case class Point(x: Int, y: Int) derives Ordering
77+
```
78+
generates the instance
79+
```scala
80+
object Point:
81+
...
82+
given Ordering[Point] = Ordering.derived
83+
```
84+
85+
86+
#### `F` and `DerivingType` have parameters of matching kind on the right
87+
This section covers cases where you can pair arguments of `F` and `DerivingType` starting from the right such that they have the same kinds pairwise, and all arguments of `F` or `DerivingType` (or both) are used up.
88+
`F` must also have at least one parameter.
89+
90+
The general shape will then be:
91+
```scala
92+
given [...]: TC[ [...] =>> DerivingType[...] ] = TC.derived
93+
```
94+
Where of course `TC` and `DerivingType` are applied to types of the correct kind.
95+
96+
To make this work, we split it into 3 cases:
97+
98+
If `F` and `DerivingType` take the same number of arguments (`N == K`):
99+
```scala
100+
given TC[DerivingType] = TC.derived
101+
// simplified form of:
102+
given TC[ [A_1, ..., A_K] => DerivingType[A_1, ..., A_K] ] = TC.derived
103+
```
104+
If `DerivingType` takes less arguments than `F` (`N < K`), we use only the rightmost parameters from the type lambda:
105+
```scala
106+
given TC[ [A_1, ..., A_K] =>> DerivingType[A_(K-N+1), ..., A_K] ] = TC.derived
107+
108+
// if DerivingType takes no arguments (N == 0), the above simplifies to:
109+
given TC[ [A_1, ..., A_K] =>> DerivingType ] = TC.derived
110+
```
111+
112+
If `F` takes less arguments than `DerivingType` (`K < N`), we fill in the remaining leftmost slots with type parameters of the given:
113+
```scala
114+
given [T_1, ... T_(N-K)]: TC[[A_1, ..., A_K] =>> DerivingType[T_1, ... T_(N-K), A_1, ..., A_K]] = TC.derived
115+
```
116+
117+
### `TC` is the `CanEqual` type class
118+
119+
We have therefore: `DerivingType[T_1, ..., T_N] derives CanEqual`.
120+
121+
Let `U_1`, ..., `U_M` be the parameters of `DerivingType` of kind `*`.
122+
(These are a subset of the `T_i`s)
123+
124+
The generated instance is then:
125+
```scala
126+
given [T_1L, T_1R, ..., T_NL, T_NR] // every parameter of DerivingType twice
127+
(using CanEqual[U_1L, U_1R], ..., CanEqual[U_ML, U_MR]): // only parameters of DerivingType with kind *
128+
CanEqual[DerivingType[T_1L, ..., T_NL], DerivingType[T_1R, ..., T_NR]] = // again, every parameter
129+
CanEqual.derived
130+
```
131+
132+
The bounds of `T_i`s are handled correctly, for example: `T_2 <: T_1` becomes `T_2L <: T_1L`.
133+
134+
For example, the class
135+
```scala
136+
class MyClass[A, G[_]](a: A, b: G[B]) derives CanEqual
137+
```
138+
generates the following given instance:
139+
```scala
140+
object MyClass:
141+
...
142+
given [A_L, A_R, G_L[_], G_R[_]](using CanEqual[A_L, A_R]): CanEqual[MyClass[A_L, G_L], MyClass[A_R, G_R]] = CanEqual.derived
143+
```
144+
145+
### `TC` is not valid for automatic derivation
146+
147+
Throw an error.
148+
149+
The exact error depends on which of the above conditions failed.
150+
As an example, if `TC` takes more than 1 parameter and is not `CanEqual`, the error is `DerivingType cannot be unified with the type argument of TC`.
151+
152+
All data types can have a `derives` clause. The rest of this document focuses primarily on data types which also have a given instance
40153
of the `Mirror` type class available.
41154

42155
## `Mirror`

0 commit comments

Comments
 (0)