@@ -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,50 +116,26 @@ 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
+ inline def [T ](x : => T ) === (y : => T )(given eq : Eq [T ]): Boolean = eq.eqv(x, y)
123
124
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
- }
125
+ implicit inline def eqGen [T ]: Eq [T ] = $ { Eq .derived[T ] }
132
126
```
133
127
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 ` :
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 e.g., the following two
130
+ types :
137
131
138
132
``` 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
- }
151
- ```
152
-
153
- The derived instance then is finally generated with:
154
-
155
- ``` scala
156
- val derivedInstance = Eq .derived(mirrorExpr)
133
+ case class Person (name : String , age : Int )
157
134
158
- ' {
159
- $derivedInstance.eqv($value, $value2)
160
- }
135
+ enum Opt [+ T ] {
136
+ case Sm (t : T )
137
+ case Nn
138
+ }
161
139
```
162
140
163
141
The full code is shown below:
@@ -167,67 +145,79 @@ import scala.deriving._
167
145
import scala .quoted ._
168
146
import scala .quoted .matching ._
169
147
170
- object Macro {
148
+ trait Eq [T ] {
149
+ def eqv (x : T , y : T ): Boolean
150
+ }
151
+
152
+ object Eq {
153
+ given Eq [String ] {
154
+ def eqv (x : String , y : String ) = x == y
155
+ }
171
156
172
- trait Eq [T ] {
173
- def eqv (x : T , y : T ) : Boolean
157
+ given Eq [Int ] {
158
+ def eqv (x : Int , y : Int ) = x == y
174
159
}
175
160
176
- object Eq {
177
- given Eq [String ] {
178
- def eqv (x : String , y : String ) = x == y
161
+ def eqProduct [ T ]( body : ( T , T ) => Boolean ) : Eq [ T ] =
162
+ new Eq [T ] {
163
+ def eqv (x : T , y : T ) : Boolean = body(x, y)
179
164
}
180
165
181
- given Eq [Int ] {
182
- def eqv (x : Int , y : Int ) = x == y
166
+ def eqSum [T ](body : (T , T ) => Boolean ): Eq [T ] =
167
+ new Eq [T ] {
168
+ def eqv (x : T , y : T ): Boolean = body(x, y)
183
169
}
184
170
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
- }
171
+ def summonAll [T ](t : Type [T ])(given qctx : QuoteContext ): List [Expr [Eq [_]]] = t match {
172
+ case ' [String *: $tpes] => ' { summon[Eq [String ]] } :: summonAll(tpes)
173
+ case ' [Int *: $tpes] => ' { summon[Eq [Int ]] } :: summonAll(tpes)
174
+ case ' [$tpe *: $tpes] => derived(given tpe , qctx) :: summonAll(tpes)
175
+ case ' [Unit ] => Nil
176
+ }
189
177
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
- }
178
+ given derived [T : Type ](given qctx : QuoteContext ): Expr [Eq [T ]] = {
179
+ import qctx .tasty .{_ , given }
194
180
195
- def derived [T : Type ](ev : Expr [Mirror .Of [T ]])(given qctx : QuoteContext ): Expr [Eq [T ]] = {
196
- import qctx .tasty .{_ , given }
181
+ val ev : Expr [Mirror .Of [T ]] = summonExpr(given ' [Mirror .Of [T ]]).get
197
182
198
- val elementTypes = ev match {
199
- case ' { $m : Mirror .ProductOf [T ] { type MirroredElemTypes = $elem } } => elem
200
- }
183
+ ev match {
184
+ case ' { $m : Mirror .ProductOf [T ] { type MirroredElemTypes = $elementTypes }} =>
185
+ val elemInstances = summonAll(elementTypes)
186
+ val eqProductBody : (Expr [T ], Expr [T ]) => Expr [Boolean ] = (x, y) => {
187
+ elemInstances.zipWithIndex.foldLeft(Expr (true : Boolean )) {
188
+ case (acc, (elem, index)) =>
189
+ val e1 = ' {$x.asInstanceOf [Product ].productElement($ {Expr (index)})}
190
+ val e2 = ' {$y.asInstanceOf [Product ].productElement($ {Expr (index)})}
201
191
202
- val elemInstances = summonAll(elementTypes)
192
+ ' { $acc && $elem.asInstanceOf [Eq [Any ]].eqv($e1, $e2) }
193
+ }
194
+ }
195
+ ' {
196
+ eqProduct((x : T , y : T ) => $ {eqProductBody(' x , ' y )})
197
+ }
203
198
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) }
199
+ case ' { $m : Mirror .SumOf [T ] { type MirroredElemTypes = $elementTypes }} =>
200
+ val elemInstances = summonAll(elementTypes)
201
+ val eqSumBody : (Expr [T ], Expr [T ]) => Expr [Boolean ] = (x, y) => {
202
+ val ordx = ' { $m.ordinal($x) }
203
+ val ordy = ' { $m.ordinal($y) }
204
+
205
+ val elements = Expr .ofList(elemInstances)
206
+ ' {
207
+ $ordx == $ordy && $elements($ordx).asInstanceOf [Eq [Any ]].eqv($x, $y)
208
+ }
210
209
}
211
- }
212
210
213
- ' {
214
- eqProduct ((x : T , y : T ) => $ {eqProductBody (' x , ' y )})
215
- }
211
+ ' {
212
+ eqSum ((x : T , y : T ) => $ {eqSumBody (' x , ' y )})
213
+ }
216
214
}
217
215
}
216
+ }
218
217
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)
218
+ object Macro3 {
219
+ inline def [T ](x : => T ) === (y : => T )(given eq : Eq [T ]): Boolean = eq.eqv(x, y)
227
220
228
- ' {
229
- $derivedInstance.eqv($value, $value2)
230
- }
231
- }
221
+ implicit inline def eqGen [T ]: Eq [T ] = $ { Eq .derived[T ] }
232
222
}
233
223
```
0 commit comments