Skip to content

Commit 4033d71

Browse files
committed
Update documentation
1 parent 06e61a9 commit 4033d71

File tree

2 files changed

+108
-121
lines changed

2 files changed

+108
-121
lines changed

docs/docs/reference/contextual/derivation-macro.md

Lines changed: 105 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ layout: doc-page
33
title: 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
77
details behind `Mirror`s and type class derivation. Here we demonstrate how to
88
implement a type class `derived` method using macros only. We follow the same
99
example of deriving `Eq` instances and for simplicity we support a `Product`
@@ -22,55 +22,57 @@ trait Eq[T] {
2222
```
2323

2424
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,
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
3534
inline given derived[T]: (m: Mirror.Of[T]) => Eq[T] = ???
3635
```
3736

3837
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:
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

6871
Note, 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
8284
quotes 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-
100102
One additional difference with the body of `derived` here as opposed to the one
101103
with `inline` is that with macros we need to synthesize the body of the code during the
102104
macro-expansion time. That is the rationale behind the `eqProductBody` function.
@@ -114,50 +116,26 @@ true
114116

115117
Following the rules in [Macros](../metaprogramming.md) we create two methods.
116118
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.
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

163141
The full code is shown below:
@@ -167,67 +145,79 @@ import scala.deriving._
167145
import scala.quoted._
168146
import 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
```

tests/run-macros/i8007/Macro_3.scala

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,9 @@ object Eq {
2626
}
2727

2828
def summonAll[T](t: Type[T])(given qctx: QuoteContext): List[Expr[Eq[_]]] = t match {
29-
case '[$tpe *: $tpes] =>
30-
val eqInstance = summonExpr(given '[Eq[$tpe]]) match {
31-
case Some(ev) => ev
32-
case None => derived(given tpe, qctx)
33-
}
34-
eqInstance :: summonAll(tpes)
29+
case '[String *: $tpes] => '{ summon[Eq[String]] } :: summonAll(tpes)
30+
case '[Int *: $tpes] => '{ summon[Eq[Int]] } :: summonAll(tpes)
31+
case '[$tpe *: $tpes] => derived(given tpe, qctx) :: summonAll(tpes)
3532
case '[Unit] => Nil
3633
}
3734

0 commit comments

Comments
 (0)