Skip to content

Commit 2b27618

Browse files
committed
Explain type lifting
Based on feedback with the early implementation, we found out that we need type lifting in the framework. This commit explains what needs to be done in the meta programming doc.
1 parent c2e5011 commit 2b27618

File tree

1 file changed

+60
-10
lines changed

1 file changed

+60
-10
lines changed

docs/docs/reference/symmetric-meta-programming.md

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,18 @@ The two types can be defined in package `scala.quoted` as follows:
7272

7373
package scala.quoted
7474

75-
abstract class Expr[T] {
75+
sealed abstract class Expr[T] {
7676
def unary_~: T // splice operation
7777
}
78-
class Type[T] {
78+
sealed abstract class Type[T] {
7979
type unary_~ = T // splice type
8080
}
8181

82+
Both `Expr` and `Type` are abstract and sealed, so all constructors for
83+
these types are provided by the system. One way to construct values of
84+
these types is by quoting, the other is by type-specific lifting
85+
operations that will be discussed later on.
86+
8287
### The Phase Consistency Principle
8388

8489
A fundamental *phase consistency principle* (PCP) regulates accesses
@@ -163,14 +168,57 @@ quote but no splice between the parameter binding of `T` and its
163168
usage. But the code can be made phase correct by adding a binding
164169
of a `Type[T]` tag:
165170

166-
def reflect[T, U](f: Expr[T] => Expr[U]): Expr[T => U] = {
167-
val Ttag = new Type[T]
168-
’{ (x: ~Ttag) => ~f(’(x))
169-
}
171+
def reflect[T, U](f: Expr[T] => Expr[U])(implicit t: Type[T]): Expr[T => U] =
172+
’{ (x: ~t) => ~f(’(x))
173+
174+
In this version of `reflect`, the type of `x` is now the result of
175+
splicing the `Type` value `t`. This operation _is_ splice correct -- there
176+
is one quote and one splice between the use of `t` and its definition.
177+
178+
To avoid clutter, the Scala implementation tries to convert any phase-incorrect
179+
reference to a type `T` to a type-splice, by rewriting `T` to `~implicitly[Type[T]]`.
180+
For instance, the user-level definition of `reflect`
181+
182+
def reflect[T: Type, U](f: Expr[T] => Expr[U]) Expr[T => U] =
183+
’{ (x: T) => ~f(’(x)) }
184+
185+
would be rewritten to
186+
187+
def reflect[T: Type, U](f: Expr[T] => Expr[U]) Expr[T => U] =
188+
’{ (x: ~implicitly[Type[T]]) => ~f(’(x)) }
189+
190+
The `implicitly` query succeeds because there is an implicit value of
191+
type `Type[T]` available (namely the evidence parameter corresponding
192+
to the context bound `: Type`), and the reference to that value is
193+
phase-correct. If that was not the case, the phase inconsistency for
194+
`T` would be reported as an error.
195+
196+
### Lifting Types
197+
198+
The previous section has shown that the metaprogramming framework has
199+
to be able to take a type `T` and convert it to a type tree of type
200+
`Type[T]` that can be reified. This means that all free variables of
201+
the type tree refer to types and values defined in the current stage.
202+
203+
For a reference to a global class, this is easy: Just issue the fully
204+
qualified name of the class. Members of reifiable types are handled by
205+
just reifying the containing type together with the member name. But
206+
what to do for references to type parameters or local type definitions
207+
that are not defined in the current stage? Here, we cannot construct
208+
the `Type[T]` tree directly, so we need to get it from a recursive
209+
implicit search. For instance, to implemenent
210+
211+
implicitly[Type[List[T]]]
212+
213+
where `T` is not defined in the current stage, we construct the type constructor
214+
of `List` applied to the splice of the result of searching for an implicit `Type[T]`:
215+
216+
'[List[~implicitly[Type[T]]]]
170217

171-
To avoid clutter, the Scala implementation will add these tags
172-
automatically in the case of a PCP violation involving types. As a consequence,
173-
types can be effectively ignored for phase consistency checking.
218+
This is in exactly the algorithm that Scala 2 uses to search for type tags.
219+
In fact Scala 2's type tag feature can be understood as a more ad-hoc version of
220+
`quoted.Type`. As was the case for type tags, the implicit search for a `quoted.Type`
221+
is handled by the compiler, using the algorithm sketched above.
174222

175223
### Example Expansion
176224

@@ -529,7 +577,9 @@ a `List` is liftable if its element type is:
529577

530578
In the end, `Liftable` resembles very much a serialization
531579
framework. Like the latter it can be derived systematically for all
532-
collections, case classes and enums.
580+
collections, case classes and enums. Note also that the implicit synthesis
581+
of "type-tag" values of type `Type[T]` is essentially the type-level
582+
analogue of lifting.
533583

534584
Using lifting, we can now give the missing definition of `showExpr` in the introductory example:
535585

0 commit comments

Comments
 (0)