Skip to content

Commit a31b0ba

Browse files
authored
Merge pull request #10641 from ShapelessCat/fix-docs-example-code
2 parents 600c25c + 2da015b commit a31b0ba

File tree

1 file changed

+56
-15
lines changed

1 file changed

+56
-15
lines changed

docs/docs/reference/metaprogramming/macros.md

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -78,21 +78,23 @@ ${'[T]} = T
7878
The type signatures of quotes and splices can be described using
7979
two fundamental types:
8080

81-
- `Expr[T]`: abstract syntax trees representing expressions of type `T`
82-
- `Type[T]`: type structures representing type `T`.
81+
- `Expr[T]`: abstract syntax trees representing expressions of type `T`
82+
- `Type[T]`: type structures representing type `T`.
8383

8484
Quoting takes expressions of type `T` to expressions of type `Expr[T]`
8585
and it takes types `T` to expressions of type `Type[T]`. Splicing
8686
takes expressions of type `Expr[T]` to expressions of type `T` and it
8787
takes expressions of type `Type[T]` to types `T`.
8888

8989
The two types can be defined in package `scala.quoted` as follows:
90+
9091
```scala
9192
package scala.quoted
9293

9394
sealed abstract class Expr[+T]
9495
sealed abstract class Type[T]
9596
```
97+
9698
Both `Expr` and `Type` are abstract and sealed, so all constructors for
9799
these types are provided by the system. One way to construct values of
98100
these types is by quoting, the other is by type-specific lifting
@@ -103,7 +105,7 @@ operations that will be discussed later on.
103105
A fundamental *phase consistency principle* (PCP) regulates accesses
104106
to free variables in quoted and spliced code:
105107

106-
- _For any free variable reference `x`, the number of quoted scopes and the number of spliced scopes between the reference to `x` and the definition of `x` must be equal_.
108+
- _For any free variable reference `x`, the number of quoted scopes and the number of spliced scopes between the reference to `x` and the definition of `x` must be equal_.
107109

108110
Here, `this`-references count as free variables. On the other
109111
hand, we assume that all imports are fully expanded and that `_root_` is
@@ -129,8 +131,8 @@ situations described above.
129131

130132
In what concerns the range of features it covers, this form of macros introduces
131133
a principled metaprogramming framework that is quite close to the MetaML family of
132-
languages. One difference is that MetaML does not have an equivalent of the PCP
133-
- quoted code in MetaML _can_ access variables in its immediately enclosing
134+
languages. One difference is that MetaML does not have an equivalent of the PCP -
135+
quoted code in MetaML _can_ access variables in its immediately enclosing
134136
environment, with some restrictions and caveats since such accesses involve
135137
serialization. However, this does not constitute a fundamental gain in
136138
expressiveness.
@@ -165,16 +167,19 @@ f2('{2}) // '{ ((x: Int) => x.toString)(2) }
165167
One limitation of `from` is that it does not β-reduce when a lambda is called immediately, as evidenced in the code `{ ((x: Int) => x.toString)(2) }`.
166168
In some cases we want to remove the lambda from the code, for this we provide the method `Expr.betaReduce` that turns a tree
167169
describing a function into a function mapping trees to trees.
170+
168171
```scala
169172
object Expr {
170173
...
171174
def betaReduce[...](...)(...): ... = ...
172175
}
173176
```
177+
174178
The definition of `Expr.betaReduce(f)(x)` is assumed to be functionally the same as
175179
`'{($f)($x)}`, however it should optimize this call by returning the
176180
result of beta-reducing `f(x)` if `f` is a known lambda expression.
177181
`Expr.betaReduce` distributes applications of `Expr` over function arrows:
182+
178183
```scala
179184
Expr.betaReduce(_): Expr[(T1, ..., Tn) => R] => ((Expr[T1], ..., Expr[Tn]) => Expr[R])
180185
```
@@ -188,10 +193,12 @@ The resulting value of `Type` will be subject to PCP.
188193
Indeed, the definition of `to` above uses `T` in the next stage, there is a
189194
quote but no splice between the parameter binding of `T` and its
190195
usage. But the code can be rewritten by adding a binding of a `Type[T]` tag:
196+
191197
```scala
192198
def to[T, R](f: Expr[T] => Expr[R])(using Type[T], Type[R], Quotes): Expr[T => R] =
193199
'{ (x: T) => ${ f('x) } }
194200
```
201+
195202
In this version of `to`, the type of `x` is now the result of
196203
splicing the `Type` value `t`. This operation _is_ splice correct -- there
197204
is one quote and one splice between the use of `t` and its definition.
@@ -222,6 +229,7 @@ phase-correct. If that was not the case, the phase inconsistency for
222229

223230
Consider the following implementation of a staged interpreter that implements
224231
a compiler through staging.
232+
225233
```scala
226234
import scala.quoted._
227235

@@ -232,15 +240,19 @@ enum Exp {
232240
case Let(x: String, e: Exp, in: Exp)
233241
}
234242
```
243+
235244
The interpreted language consists of numbers `Num`, addition `Plus`, and variables
236245
`Var` which are bound by `Let`. Here are two sample expressions in the language:
246+
237247
```scala
238248
val exp = Plus(Plus(Num(2), Var("x")), Num(4))
239249
val letExp = Let("x", Num(3), exp)
240250
```
251+
241252
Here’s a compiler that maps an expression given in the interpreted
242253
language to quoted Scala code of type `Expr[Int]`.
243254
The compiler takes an environment that maps variable names to Scala `Expr`s.
255+
244256
```scala
245257
import scala.quoted._
246258

@@ -255,17 +267,21 @@ def compile(e: Exp, env: Map[String, Expr[Int]])(using Quotes): Expr[Int] = e ma
255267
'{ val y = ${ compile(e, env) }; ${ compile(body, env + (x -> 'y)) } }
256268
}
257269
```
270+
258271
Running `compile(letExp, Map())` would yield the following Scala code:
272+
259273
```scala
260274
'{ val y = 3; (2 + y) + 4 }
261275
```
276+
262277
The body of the first clause, `case Num(n) => Expr(n)`, looks suspicious. `n`
263278
is declared as an `Int`, yet it is converted to an `Expr[Int]` with `Expr()`.
264279
Shouldn’t `n` be quoted? In fact this would not
265280
work since replacing `n` by `'n` in the clause would not be phase
266281
correct.
267282

268283
The `Expr.apply` method is defined in package `quoted`:
284+
269285
```scala
270286
package quoted
271287

@@ -275,6 +291,7 @@ object Expr {
275291
...
276292
}
277293
```
294+
278295
This method says that values of types implementing the `ToExpr` type class can be
279296
converted to `Expr` values using `Expr.apply`.
280297

@@ -287,15 +304,18 @@ efficiency. But the `ToExpr` instances are nevertheless not _magic_
287304
in the sense that they could all be defined in a user program without
288305
knowing anything about the representation of `Expr` trees. For
289306
instance, here is a possible instance of `ToExpr[Boolean]`:
307+
290308
```scala
291309
given ToExpr[Boolean] {
292310
def toExpr(b: Boolean) =
293311
if (b) '{ true } else '{ false }
294312
}
295313
```
314+
296315
Once we can lift bits, we can work our way up. For instance, here is a
297316
possible implementation of `ToExpr[Int]` that does not use the underlying
298317
tree machinery:
318+
299319
```scala
300320
given ToExpr[Int] {
301321
def toExpr(n: Int) = n match {
@@ -307,28 +327,33 @@ given ToExpr[Int] {
307327
}
308328
}
309329
```
330+
310331
Since `ToExpr` is a type class, its instances can be conditional. For example,
311332
a `List` is liftable if its element type is:
333+
312334
```scala
313335
given [T: ToExpr : Type]: ToExpr[List[T]] with
314336
def toExpr(xs: List[T]) = xs match {
315337
case head :: tail => '{ ${ Expr(head) } :: ${ toExpr(tail) } }
316338
case Nil => '{ Nil: List[T] }
317339
}
318340
```
341+
319342
In the end, `ToExpr` resembles very much a serialization
320343
framework. Like the latter it can be derived systematically for all
321344
collections, case classes and enums. Note also that the synthesis
322345
of _type-tag_ values of type `Type[T]` is essentially the type-level
323346
analogue of lifting.
324347

325348
Using lifting, we can now give the missing definition of `showExpr` in the introductory example:
349+
326350
```scala
327351
def showExpr[T](expr: Expr[T])(using Quotes): Expr[String] = {
328352
val code: String = expr.show
329353
Expr(code)
330354
}
331355
```
356+
332357
That is, the `showExpr` method converts its `Expr` argument to a string (`code`), and lifts
333358
the result back to an `Expr[String]` using `Expr.apply`.
334359

@@ -346,14 +371,18 @@ what to do for references to type parameters or local type definitions
346371
that are not defined in the current stage? Here, we cannot construct
347372
the `Type[T]` tree directly, so we need to get it from a recursive
348373
implicit search. For instance, to implement
374+
349375
```scala
350376
summon[Type[List[T]]]
351377
```
378+
352379
where `T` is not defined in the current stage, we construct the type constructor
353380
of `List` applied to the splice of the result of searching for a given instance for `Type[T]`:
381+
354382
```scala
355383
'[ List[ ${ summon[Type[T]] } ] ]
356384
```
385+
357386
This is exactly the algorithm that Scala 2 uses to search for type tags.
358387
In fact Scala 2's type tag feature can be understood as a more ad-hoc version of
359388
`quoted.Type`. As was the case for type tags, the implicit search for a `quoted.Type`
@@ -386,13 +415,16 @@ object App {
386415
}
387416
}
388417
```
418+
389419
Inlining the `assert` function would give the following program:
420+
390421
```scala
391422
val program = {
392423
val x = 1
393424
${ Macros.assertImpl('{ x != 0) } }
394425
}
395426
```
427+
396428
The example is only phase correct because `Macros` is a global value and
397429
as such not subject to phase consistency checking. Conceptually that’s
398430
a bit unsatisfactory. If the PCP is so fundamental, it should be
@@ -408,12 +440,14 @@ macros would be to have the user program be in a phase after the macro
408440
definitions, reflecting the fact that macros have to be defined and
409441
compiled before they are used. Hence, conceptually the program part
410442
should be treated by the compiler as if it was quoted:
443+
411444
```scala
412445
val program = '{
413446
val x = 1
414447
${ Macros.assertImpl('{ x != 0 }) }
415448
}
416449
```
450+
417451
If `program` is treated as a quoted expression, the call to
418452
`Macro.assertImpl` becomes phase correct even if macro library and
419453
program are conceptualized as local definitions.
@@ -438,6 +472,7 @@ expression contains value. Otherwise it will retrun `None` (or emit an error).
438472
To avoid having incidental val bindings generated by the inlining of the `def`
439473
it is recommended to use an inline parameter. To illustrate this, consider an
440474
implementation of the `power` function that makes use of a statically known exponent:
475+
441476
```scala
442477
inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) }
443478

@@ -482,6 +517,7 @@ that invokation of `run` in splices. Consider the following expression:
482517
```scala
483518
'{ (x: Int) => ${ run('x); 1 } }
484519
```
520+
485521
This is again phase correct, but will lead us into trouble. Indeed, evaluating
486522
the splice will reduce the expression `run('x)` to `x`. But then the result
487523

@@ -566,6 +602,7 @@ sum
566602
```
567603

568604
Finally cleanups and dead code elimination:
605+
569606
```scala
570607
val arr: Array[Int] = Array.apply(1, [2,3 : Int]:Int*)
571608
var sum = 0
@@ -632,12 +669,12 @@ It is possible to deconstruct or extract values out of `Expr` using pattern matc
632669

633670
`scala.quoted` contains objects that can help extracting values from `Expr`.
634671

635-
* `scala.quoted.Expr`/`scala.quoted.Exprs`: matches an expression of a value (or list of values) and returns the value (or list of values).
636-
* `scala.quoted.Const`/`scala.quoted.Consts`: Same as `Expr`/`Exprs` but only works on primitive values.
637-
* `scala.quoted.Varargs`: matches an explicit sequence of expressions and returns them. These sequences are useful to get individual `Expr[T]` out of a varargs expression of type `Expr[Seq[T]]`.
638-
672+
- `scala.quoted.Expr`/`scala.quoted.Exprs`: matches an expression of a value (or list of values) and returns the value (or list of values).
673+
- `scala.quoted.Const`/`scala.quoted.Consts`: Same as `Expr`/`Exprs` but only works on primitive values.
674+
- `scala.quoted.Varargs`: matches an explicit sequence of expressions and returns them. These sequences are useful to get individual `Expr[T]` out of a varargs expression of type `Expr[Seq[T]]`.
639675

640676
These could be used in the following way to optimize any call to `sum` that has statically known values.
677+
641678
```scala
642679
inline def sum(inline args: Int*): Int = ${ sumExpr('args) }
643680
private def sumExpr(argsExpr: Expr[Seq[Int]])(using Quotes): Expr[Int] = argsExpr match {
@@ -646,7 +683,7 @@ private def sumExpr(argsExpr: Expr[Seq[Int]])(using Quotes): Expr[Int] = argsExp
646683
// argValues is of type Seq[Int]
647684
Expr(argValues.sum) // precompute result of sum
648685
case Varargs(argExprs) => // argExprs is of type Seq[Expr[Int]]
649-
val staticSum: Int = argExprs.map(_.value.getOrElse(0))
686+
val staticSum: Int = argExprs.map(_.value.getOrElse(0)).sum
650687
val dynamicSum: Seq[Expr[Int]] = argExprs.filter(_.value.isEmpty)
651688
dynamicSum.foldLeft(Expr(staticSum))((acc, arg) => '{ $acc + $arg })
652689
case _ =>
@@ -660,6 +697,7 @@ Quoted pattens allow deconstructing complex code that contains a precise structu
660697
Patterns `'{ ... }` can be placed in any location where Scala expects a pattern.
661698

662699
For example
700+
663701
```scala
664702
optimize {
665703
sum(sum(1, a, 2), 3, b)
@@ -695,7 +733,7 @@ private def sumExpr(args1: Seq[Expr[Int]])(using Quotes): Expr[Int] = {
695733
Sometimes it is necessary to get a more precise type for an expression. This can be achived using the following pattern match.
696734

697735
```scala
698-
def f(exp: Expr[Any])(using Quotes) =
736+
def f(expr: Expr[Any])(using Quotes) =
699737
expr match
700738
case '{ $x: t } =>
701739
// If the pattern match succeeds, then there is some type `t` such that
@@ -713,10 +751,11 @@ private def showMeExpr(sc: Expr[StringContext], argsExpr: Expr[Seq[Any]])(using
713751
argsExpr match {
714752
case Varargs(argExprs) =>
715753
val argShowedExprs = argExprs.map {
716-
case '{ $arg: t } =>
717-
Expr.summon[Show[t]] match {
754+
case '{ $arg: tp } =>
755+
val showTp = Type.of[Show[tp]]
756+
Expr.summon(using showTp) match {
718757
case Some(showExpr) => '{ $showExpr.show($arg) }
719-
case None => report.error(s"could not find implicit for ${showTp.show}", arg); '{???}
758+
case None => report.error(s"could not find implicit for ${Type.show[Show[tp]]}", arg); '{???}
720759
}
721760
}
722761
val newArgsExpr = Varargs(argShowedExprs)
@@ -746,9 +785,11 @@ then the rest of the quote can refer to this definition.
746785
```
747786

748787
To match such a term we need to match the definition and the rest of the code, but we need to explicitly state that the rest of the code may refer to this definition.
788+
749789
```scala
750790
case '{ val y: Int = $x; $body(y): Int } =>
751791
```
792+
752793
Here `$x` will match any closed expression while `$body(y)` will match an expression that is closed under `y`. Then
753794
the subexpression of type `Expr[Int]` is bound to `body` as an `Expr[Int => Int]`. The extra argument represents the references to `y`. Usually this expression is used in combination with `Expr.betaReduce` to replace the extra argument.
754795

@@ -759,7 +800,7 @@ private def evalExpr(e: Expr[Int])(using Quotes): Expr[Int] = {
759800
e match {
760801
case '{ val y: Int = $x; $body(y): Int } =>
761802
// body: Expr[Int => Int] where the argument represents references to y
762-
evalExpr(Expr.betaReduce(body)(evalExpr(x)))
803+
evalExpr(Expr.betaReduce('{$body(${evalExpr(x)})}))
763804
case '{ ($x: Int) * ($y: Int) } =>
764805
(x.value, y.value) match
765806
case (Some(a), Some(b)) => Expr(a * b)

0 commit comments

Comments
 (0)