@@ -3,7 +3,7 @@ layout: doc-page
33title : How to write a type class `derived` method using macros
44---
55
6- In the main [ derivation] ( ./derivation.md ) documentation page we explaind the
6+ In the main [ derivation] ( ./derivation.md ) documentation page, we explained the
77details behind ` Mirror ` s and type class derivation. Here we demonstrate how to
88implement a type class ` derived ` method using macros only. We follow the same
99example of deriving ` Eq ` instances and for simplicity we support a ` Product `
@@ -22,55 +22,57 @@ trait Eq[T] {
2222```
2323
2424we 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,
2726
2827``` 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 ]]
3029```
3130
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 ` :
3332
3433``` scala
3534inline given derived [T ]: (m : Mirror .Of [T ]) => Eq [T ] = ???
3635```
3736
3837Note, 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:
4141
4242
4343``` 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 ]] = {
4545 import qctx .tasty .{_ , given }
4646
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
5048
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)})}
5257
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+ }
6164
62- ' {
63- eqProduct(( x : T , y : T ) => $ {eqProductBody( ' x , ' y )})
65+ // case for Mirror.ProductOf[T]
66+ // ...
6467 }
6568}
6669```
6770
6871Note, that in the ` inline ` case we can merely write
6972` 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:
7476
7577``` scala
7678' {
@@ -82,21 +84,21 @@ Instead we extract the tuple-type for element types using pattern matching over
8284quotes and more specifically of the refined type:
8385
8486``` scala
85- case ' { $m : Mirror .ProductOf [T ] { type MirroredElemTypes = $elem } } => elem
87+ case ' { $m : Mirror .ProductOf [T ] { type MirroredElemTypes = $elementTypes } } => ...
8688```
8789
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:
8992
9093``` 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+ }
95100```
96101
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-
100102One additional difference with the body of ` derived ` here as opposed to the one
101103with ` inline ` is that with macros we need to synthesize the body of the code during the
102104macro-expansion time. That is the rationale behind the ` eqProductBody ` function.
@@ -114,50 +116,26 @@ true
114116
115117Following the rules in [ Macros] ( ../metaprogramming.md ) we create two methods.
116118One 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.
117121
118122``` 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)
123124
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 ] }
132126```
133127
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 :
137131
138132``` 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 )
157134
158- ' {
159- $derivedInstance.eqv($value, $value2)
160- }
135+ enum Opt [+ T ] {
136+ case Sm (t : T )
137+ case Nn
138+ }
161139```
162140
163141The full code is shown below:
@@ -167,67 +145,79 @@ import scala.deriving._
167145import scala .quoted ._
168146import scala .quoted .matching ._
169147
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+ }
171156
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
174159 }
175160
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)
179164 }
180165
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)
183169 }
184170
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+ }
189177
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 }
194180
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
197182
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)})}
201191
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+ }
203198
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+ }
210209 }
211- }
212210
213- ' {
214- eqProduct ((x : T , y : T ) => $ {eqProductBody (' x , ' y )})
215- }
211+ ' {
212+ eqSum ((x : T , y : T ) => $ {eqSumBody (' x , ' y )})
213+ }
216214 }
217215 }
216+ }
218217
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)
227220
228- ' {
229- $derivedInstance.eqv($value, $value2)
230- }
231- }
221+ implicit inline def eqGen [T ]: Eq [T ] = $ { Eq .derived[T ] }
232222}
233223```
0 commit comments