@@ -3,7 +3,7 @@ layout: doc-page
3
3
title : How to write a type class `derived` method using macros
4
4
---
5
5
6
- In the main [ derivation] ( ./derivation.md ) documentation page we explaind the
6
+ In the main [ derivation] ( ./derivation.md ) documentation page, we explained the
7
7
details behind ` Mirror ` s and type class derivation. Here we demonstrate how to
8
8
implement a type class ` derived ` method using macros only. We follow the same
9
9
example of deriving ` Eq ` instances and for simplicity we support a ` Product `
@@ -22,55 +22,57 @@ trait Eq[T] {
22
22
```
23
23
24
24
we need to implement a method ` Eq.derived ` on the companion object of ` Eq ` that
25
- produces an instance for ` Eq[T] ` given a ` Mirror[T] ` . Here is a possible
26
- signature,
25
+ produces a quoted instance for ` Eq[T] ` . Here is a possible signature,
27
26
28
27
``` scala
29
- def derived [T : Type ](ev : Expr [ Mirror . Of [ T ]])( given qctx : QuoteContext ): Expr [Eq [T ]] = ???
28
+ given derived [T : Type ](given qctx : QuoteContext ): Expr [Eq [T ]]
30
29
```
31
30
32
- and for comparison reasons we give the same signature with had with ` inline ` :
31
+ and for comparison reasons we give the same signature we had with ` inline ` :
33
32
34
33
``` scala
35
34
inline given derived [T ]: (m : Mirror .Of [T ]) => Eq [T ] = ???
36
35
```
37
36
38
37
Note, that since a type is used in a subsequent stage it will need to be lifted
39
- to a ` Type ` by using the corresponding context bound. The body of this method is
40
- shown below:
38
+ to a ` Type ` by using the corresponding context bound. Also, not that we can
39
+ summon the quoted ` Mirror ` inside the body of the ` derived ` this we can omit it
40
+ from the signature. The body of the ` derived ` method is shown below:
41
41
42
42
43
43
``` scala
44
- def derived [T : Type ]( m : Expr [ Mirror . Of [ T ]]) (given qctx : QuoteContext ): Expr [Eq [T ]] = {
44
+ given derived [T : Type ](given qctx : QuoteContext ): Expr [Eq [T ]] = {
45
45
import qctx .tasty .{_ , given }
46
46
47
- val elementTypes = m match {
48
- case ' { $m : Mirror .ProductOf [T ] { type MirroredElemTypes = $elem } } => elem
49
- }
47
+ val ev : Expr [Mirror .Of [T ]] = summonExpr(given ' [Mirror .Of [T ]]).get
50
48
51
- val elemInstances = summonAll(elementTypes)
49
+ ev match {
50
+ case ' { $m : Mirror .ProductOf [T ] { type MirroredElemTypes = $elementTypes }} =>
51
+ val elemInstances = summonAll(elementTypes)
52
+ val eqProductBody : (Expr [T ], Expr [T ]) => Expr [Boolean ] = (x, y) => {
53
+ elemInstances.zipWithIndex.foldLeft(Expr (true : Boolean )) {
54
+ case (acc, (elem, index)) =>
55
+ val e1 = ' {$x.asInstanceOf [Product ].productElement($ {Expr (index)})}
56
+ val e2 = ' {$y.asInstanceOf [Product ].productElement($ {Expr (index)})}
52
57
53
- val eqProductBody : (Expr [T ], Expr [T ]) => Expr [Boolean ] = (x, y) => {
54
- elemInstances.zipWithIndex.foldLeft(Expr (true : Boolean )) {
55
- case (acc, (elem, index)) =>
56
- val e1 = ' {$x.asInstanceOf [Product ].productElement($ {Expr (index)})}
57
- val e2 = ' {$y.asInstanceOf [Product ].productElement($ {Expr (index)})}
58
- ' { $acc && $elem.asInstanceOf [Eq [Any ]].eqv($e1, $e2) }
59
- }
60
- }
58
+ ' { $acc && $elem.asInstanceOf [Eq [Any ]].eqv($e1, $e2) }
59
+ }
60
+ }
61
+ ' {
62
+ eqProduct((x : T , y : T ) => $ {eqProductBody(' x , ' y )})
63
+ }
61
64
62
- ' {
63
- eqProduct(( x : T , y : T ) => $ {eqProductBody( ' x , ' y )})
65
+ // case for Mirror.ProductOf[T]
66
+ // ...
64
67
}
65
68
}
66
69
```
67
70
68
71
Note, that in the ` inline ` case we can merely write
69
72
` summonAll[m.MirroredElemTypes] ` inside the inline method but here, since
70
- ` summonExpr ` is required if we need to query the context we need to extract the
71
- element types in a macro fashion. Being inside a macro, our first reaction would
72
- be to write the code below. Since the path inside the type argument is not
73
- stable this cannot be used:
73
+ ` summonExpr ` is required, we can extract the element types in a macro fashion.
74
+ Being inside a macro, our first reaction would be to write the code below. Since
75
+ the path inside the type argument is not stable this cannot be used:
74
76
75
77
``` scala
76
78
' {
@@ -82,21 +84,21 @@ Instead we extract the tuple-type for element types using pattern matching over
82
84
quotes and more specifically of the refined type:
83
85
84
86
``` scala
85
- case ' { $m : Mirror .ProductOf [T ] { type MirroredElemTypes = $elem } } => elem
87
+ case ' { $m : Mirror .ProductOf [T ] { type MirroredElemTypes = $elementTypes } } => ...
86
88
```
87
89
88
- The implementation of ` summonAll ` as a macro can be show below:
90
+ The implementation of ` summonAll ` as a macro can be show below assuming that we
91
+ have the given instances for our primitive types:
89
92
90
93
``` scala
91
- def summonAll [T ](t : Type [T ])(given qctx : QuoteContext ): List [Expr [Eq [_]]] = t match {
92
- case ' [$tpe *: $tpes] => summonExpr(given ' [Eq [$tpe]]).get :: summonAll(tpes)
93
- case ' [Unit ] => Nil
94
- }
94
+ def summonAll [T ](t : Type [T ])(given qctx : QuoteContext ): List [Expr [Eq [_]]] = t match {
95
+ case ' [String *: $tpes] => ' { summon[Eq [String ]] } :: summonAll(tpes)
96
+ case ' [Int *: $tpes] => ' { summon[Eq [Int ]] } :: summonAll(tpes)
97
+ case ' [$tpe *: $tpes] => derived(given tpe , qctx) :: summonAll(tpes)
98
+ case ' [Unit ] => Nil
99
+ }
95
100
```
96
101
97
- Note, that in a realistic implementation the ` summonExpr(given '[Eq[$tpe]]).get `
98
- is going to fail if the necessary given instances for some type are not present.
99
-
100
102
One additional difference with the body of ` derived ` here as opposed to the one
101
103
with ` inline ` is that with macros we need to synthesize the body of the code during the
102
104
macro-expansion time. That is the rationale behind the ` eqProductBody ` function.
@@ -114,51 +116,18 @@ true
114
116
115
117
Following the rules in [ Macros] ( ../metaprogramming.md ) we create two methods.
116
118
One that hosts the top-level splice ` eqv ` and one that is the implementation.
119
+ Alternatively and what is shown below is that we can call the ` eqv ` method
120
+ directly. The ` eqGen ` can trigger the derivation.
117
121
118
122
``` scala
119
- inline def eqv [T ](value : => T , value2 : => T ): Boolean = $ { eqvImpl(' value , ' value2 ) }
120
-
121
- def eqvImpl [T : Type ](value : Expr [T ], value2 : Expr [T ])(given qctx : QuoteContext ): Expr [Boolean ] = {
122
- import qctx .tasty .{_ , given }
123
-
124
- val mirrorTpe = ' [Mirror .Of [T ]]
125
- val mirrorExpr = summonExpr(given mirrorTpe ).get
126
- val derivedInstance = Eq .derived(mirrorExpr)
127
-
128
- ' {
129
- $derivedInstance.eqv($value, $value2)
130
- }
131
- }
132
- ```
133
-
134
- Note, that we need to quote the type we need ` Mirror.Of[T] ` with the quoted
135
- syntax for types and then trigger its synthesis with ` summonExpr ` . ` mirrorExpr `
136
- now holds the refined type for e.g., a ` Person ` :
123
+ inline def [T ](x : => T ) === (y : => T )(given eq : Eq [T ]): Boolean = eq.eqv(x, y)
137
124
138
- ``` scala
139
- scala.deriving.Mirror {
140
- type MirroredType >: Person <: Person
141
- type MirroredMonoType >: Person <: Person
142
- type MirroredElemTypes >: scala.Nothing <: scala.Tuple
143
- } & scala.deriving.Mirror .Product {
144
- type MirroredMonoType >: Person <: Person
145
- type MirroredType >: Person <: Person
146
- type MirroredLabel >: " Person" <: " Person"
147
- } {
148
- type MirroredElemTypes >: scala.*: [scala.Predef .String , scala.*: [scala.Int , scala.Unit ]] <: scala.*: [scala.Predef .String , scala.*: [scala.Int , scala.Unit ]]
149
- type MirroredElemLabels >: scala.*: [" name" , scala.*: [" age" , scala.Unit ]] <: scala.*: [" name" , scala.*: [" age" , scala.Unit ]]
150
- }
125
+ implicit inline def eqGen [T ]: Eq [T ] = $ { Eq .derived[T ] }
151
126
```
152
127
153
- The derived instance then is finally generated with:
154
-
155
- ``` scala
156
- val derivedInstance = Eq .derived(mirrorExpr)
157
-
158
- ' {
159
- $derivedInstance.eqv($value, $value2)
160
- }
161
- ```
128
+ Note, that we use inline method syntax and we can compare instance such as
129
+ ` Sm(Person("Test", 23)) === Sm(Person("Test", 24)) ` for given ` Option ` (enum)
130
+ and ` Person ` (case class) types.
162
131
163
132
The full code is shown below:
164
133
@@ -167,67 +136,79 @@ import scala.deriving._
167
136
import scala .quoted ._
168
137
import scala .quoted .matching ._
169
138
170
- object Macro {
139
+ trait Eq [T ] {
140
+ def eqv (x : T , y : T ): Boolean
141
+ }
142
+
143
+ object Eq {
144
+ given Eq [String ] {
145
+ def eqv (x : String , y : String ) = x == y
146
+ }
171
147
172
- trait Eq [T ] {
173
- def eqv (x : T , y : T ) : Boolean
148
+ given Eq [Int ] {
149
+ def eqv (x : Int , y : Int ) = x == y
174
150
}
175
151
176
- object Eq {
177
- given Eq [String ] {
178
- def eqv (x : String , y : String ) = x == y
152
+ def eqProduct [ T ]( body : ( T , T ) => Boolean ) : Eq [ T ] =
153
+ new Eq [T ] {
154
+ def eqv (x : T , y : T ) : Boolean = body(x, y)
179
155
}
180
156
181
- given Eq [Int ] {
182
- def eqv (x : Int , y : Int ) = x == y
157
+ def eqSum [T ](body : (T , T ) => Boolean ): Eq [T ] =
158
+ new Eq [T ] {
159
+ def eqv (x : T , y : T ): Boolean = body(x, y)
183
160
}
184
161
185
- def eqProduct [T ](body : (T , T ) => Boolean ): Eq [T ] =
186
- new Eq [T ] {
187
- def eqv (x : T , y : T ): Boolean = body(x, y)
188
- }
162
+ def summonAll [T ](t : Type [T ])(given qctx : QuoteContext ): List [Expr [Eq [_]]] = t match {
163
+ case ' [String *: $tpes] => ' { summon[Eq [String ]] } :: summonAll(tpes)
164
+ case ' [Int *: $tpes] => ' { summon[Eq [Int ]] } :: summonAll(tpes)
165
+ case ' [$tpe *: $tpes] => derived(given tpe , qctx) :: summonAll(tpes)
166
+ case ' [Unit ] => Nil
167
+ }
189
168
190
- def summonAll [T ](t : Type [T ])(given qctx : QuoteContext ): List [Expr [Eq [_]]] = t match {
191
- case ' [$tpe *: $tpes] => summonExpr(given ' [Eq [$tpe]]).get :: summonAll(tpes)
192
- case ' [Unit ] => Nil
193
- }
169
+ given derived [T : Type ](given qctx : QuoteContext ): Expr [Eq [T ]] = {
170
+ import qctx .tasty .{_ , given }
194
171
195
- def derived [T : Type ](ev : Expr [Mirror .Of [T ]])(given qctx : QuoteContext ): Expr [Eq [T ]] = {
196
- import qctx .tasty .{_ , given }
172
+ val ev : Expr [Mirror .Of [T ]] = summonExpr(given ' [Mirror .Of [T ]]).get
197
173
198
- val elementTypes = ev match {
199
- case ' { $m : Mirror .ProductOf [T ] { type MirroredElemTypes = $elem } } => elem
200
- }
174
+ ev match {
175
+ case ' { $m : Mirror .ProductOf [T ] { type MirroredElemTypes = $elementTypes }} =>
176
+ val elemInstances = summonAll(elementTypes)
177
+ val eqProductBody : (Expr [T ], Expr [T ]) => Expr [Boolean ] = (x, y) => {
178
+ elemInstances.zipWithIndex.foldLeft(Expr (true : Boolean )) {
179
+ case (acc, (elem, index)) =>
180
+ val e1 = ' {$x.asInstanceOf [Product ].productElement($ {Expr (index)})}
181
+ val e2 = ' {$y.asInstanceOf [Product ].productElement($ {Expr (index)})}
201
182
202
- val elemInstances = summonAll(elementTypes)
183
+ ' { $acc && $elem.asInstanceOf [Eq [Any ]].eqv($e1, $e2) }
184
+ }
185
+ }
186
+ ' {
187
+ eqProduct((x : T , y : T ) => $ {eqProductBody(' x , ' y )})
188
+ }
203
189
204
- val eqProductBody : (Expr [T ], Expr [T ]) => Expr [Boolean ] = (x, y) => {
205
- elemInstances.zipWithIndex.foldLeft(Expr (true : Boolean )) {
206
- case (acc, (elem, index)) =>
207
- val e1 = ' {$x.asInstanceOf [Product ].productElement($ {Expr (index)})}
208
- val e2 = ' {$y.asInstanceOf [Product ].productElement($ {Expr (index)})}
209
- ' { $acc && $elem.asInstanceOf [Eq [Any ]].eqv($e1, $e2) }
190
+ case ' { $m : Mirror .SumOf [T ] { type MirroredElemTypes = $elementTypes }} =>
191
+ val elemInstances = summonAll(elementTypes)
192
+ val eqSumBody : (Expr [T ], Expr [T ]) => Expr [Boolean ] = (x, y) => {
193
+ val ordx = ' { $m.ordinal($x) }
194
+ val ordy = ' { $m.ordinal($y) }
195
+
196
+ val elements = Expr .ofList(elemInstances)
197
+ ' {
198
+ $ordx == $ordy && $elements($ordx).asInstanceOf [Eq [Any ]].eqv($x, $y)
199
+ }
210
200
}
211
- }
212
201
213
- ' {
214
- eqProduct ((x : T , y : T ) => $ {eqProductBody (' x , ' y )})
215
- }
202
+ ' {
203
+ eqSum ((x : T , y : T ) => $ {eqSumBody (' x , ' y )})
204
+ }
216
205
}
217
206
}
207
+ }
218
208
219
- inline def eqv [T ](value : => T , value2 : => T ): Boolean = $ { eqvImpl(' value , ' value2 ) }
220
-
221
- def eqvImpl [T : Type ](value : Expr [T ], value2 : Expr [T ])(given qctx : QuoteContext ): Expr [Boolean ] = {
222
- import qctx .tasty .{_ , given }
223
-
224
- val mirrorTpe = ' [Mirror .Of [T ]]
225
- val mirrorExpr = summonExpr(given mirrorTpe ).get
226
- val derivedInstance = Eq .derived(mirrorExpr)
209
+ object Macro3 {
210
+ inline def [T ](x : => T ) === (y : => T )(given eq : Eq [T ]): Boolean = eq.eqv(x, y)
227
211
228
- ' {
229
- $derivedInstance.eqv($value, $value2)
230
- }
231
- }
212
+ implicit inline def eqGen [T ]: Eq [T ] = $ { Eq .derived[T ] }
232
213
}
233
214
```
0 commit comments