From 80a59f7a269234b1a664ac940be9579df743bf5a Mon Sep 17 00:00:00 2001 From: Zeimyth Date: Mon, 12 Jul 2021 14:00:40 -0600 Subject: [PATCH 1/4] Clean up grammar and other nit issues in quotes.md --- _overviews/scala3-macros/tutorial/quotes.md | 173 ++++++++++---------- 1 file changed, 89 insertions(+), 84 deletions(-) diff --git a/_overviews/scala3-macros/tutorial/quotes.md b/_overviews/scala3-macros/tutorial/quotes.md index 478bdf4778..0c4ef57d16 100644 --- a/_overviews/scala3-macros/tutorial/quotes.md +++ b/_overviews/scala3-macros/tutorial/quotes.md @@ -9,8 +9,8 @@ next-page: reflection ## Code blocks A quoted code block `'{ ... }` is syntactically similar to a string quote `" ... "` with the difference that the first contains typed code. -To insert a code into other code we use the `$expr` or `${ expr }` where `expr` is of type `Expr[T]`. -Intuitively, the code directly within the quote is not executed now, while the code within the splices is evaluated and their results are then spliced into the surrounding expression. +To insert code into other code, we can use the syntax `$expr` or `${ expr }`, where `expr` is of type `Expr[T]`. +Intuitively, the code directly within the quote (`'{ ... }`) is not executed now, while the code within the splice (`${ ... }`) is evaluated and the results spliced into the surrounding expression. ```scala val msg = Expr("Hello") @@ -19,15 +19,14 @@ println(printHello.show) // print("Hello") ``` In general, the quote delays the execution while the splice makes it happen before the surrounding code. -This generalisation allows us to also give meaning to a `${ .. }` that is not within a quote, this evaluate the code within the splice at compile-time and place the result in the generated code. -Due to some technical considerations we only allow it directly within `inline` definitions that we call a [macro][macros]. +This generalisation allows us to also give meaning to a `${ ... }` that is not within a quote. This evaluates the code within the splice at compile-time and places the result in the generated code. +Due to some technical considerations, only non-quoted splices are allowed directly within `inline` definitions that we call a [macro][macros]. -It is possible to write a quote within a quote, but usually when we write macros we do not encounter such code. +It is possible to write a quote within a quote, but this pattern is not common when writing macros. ## Level consistency -One cannot simply write any arbitrary code within quotes and within splices. -A part of the program will live at compile-time and the other will live at runtime. -Consider the following ill-constructed code. +One cannot simply write any arbitrary code within quotes and within splices, as one part of the program will live at compile-time and the other will live at runtime. +Consider the following ill-constructed code: ```scala def myBadCounter1(using Quotes): Expr[Int] = { @@ -36,9 +35,9 @@ def myBadCounter1(using Quotes): Expr[Int] = { } ``` The problem with this code is that `x` exists during compilation, but then we try to use it after the compiler has finished (maybe even in another machine). -Clearly it would be impossible to access its value and update it. +Clearly, it would be impossible to access its value and update it. -Now consider the dual version, where we define the variable at runtime and try to access it at compile-time. +Now consider the dual version, where we define the variable at runtime and try to access it at compile-time: ```scala def myBadCounter2(using Quotes): Expr[Int] = '{ var x = 0 @@ -62,58 +61,58 @@ We introduce _levels_ as a count of the number of quotes minus the number of spl } ``` -The system will allow at any level references to global definitions such as `println`, but will restrict references to local definitions. +The system will allow references to global definitions such as `println` at any level, but will restrict references to local definitions. A local definition can only be accessed if it is defined at the same level as its reference. This will catch the errors in `myBadCounter1` and `myBadCounter2`. -Even though we cannot refer to variable inside of a quote, we can still pass its current value to it by lifting the value to an expression using `Expr.apply`. +Even though we cannot refer to a variable inside of a quote, we can still pass its current value through a quote by lifting the value to an expression using `Expr.apply`. ## Generics -When using type parameters or other kinds of abstract types with quoted code we will need to keep track of some of these types explicitly. +When using type parameters or other kinds of abstract types with quoted code, we will need to keep track of some of these types explicitly. Scala uses erased-types semantics for its generics. This implies that types are removed from the program when compiling and the runtime does not have to track all types at runtime. -Consider the following code +Consider the following code: ```scala -def evalAndUse[T](x: Expr[T]) = '{ +def evalAndUse[T](x: Expr[T])(using Quotes) = '{ val x2: T = $x // error ... // use x2 } ``` -Here we will get an error telling us that we are missing a contextual `Type[T]`. -Therefore we can easily fix it by writing +Here, we will get an error telling us that we are missing a contextual `Type[T]`. +Therefore, we can easily fix it by writing: ```scala -def evalAndUse[X](x: Expr[X])(using Type[X])(using Quotes) = '{ - val x2: X = $x +def evalAndUse[T](x: Expr[T])(using Type[T])(using Quotes) = '{ + val x2: T = $x ... // use x2 } ``` -This code will be equivalent to the more verbose +This code will be equivalent to this more verbose version: ```scala -def evalAndUse[X](x: Expr[X])(using t: Type[X])(using Quotes) = '{ +def evalAndUse[T](x: Expr[T])(using t: Type[T])(using Quotes) = '{ val x2: t.Underlying = $x ... // use x2 } ``` -Note that `Type` has a type member called `Underlying` that refers to the type held within the `Type`, in this case `t.Underlying` is `X`. -Note that even if we used it implicitly is better to keep it contextual as some changes inside the quote may require it. +Note that `Type` has a type member called `Underlying` that refers to the type held within the `Type`; in this case, `t.Underlying` is `T`. +Even if we use the `Type` implicitly, is generally better to keep it contextual as some changes inside the quote may require it. The less verbose version is usually the best way to write the types as it is much simpler to read. -In some cases, we will not know statically the type within the `Type` and will need to use the `.T` to refer to it. +In some cases, we will not statically know the type within the `Type` and will need to use the `t.Underlying` to refer to it. When do we need this extra `Type` parameter? -* When a type is abstract and it is used in a level that is larger than the current level. +* When a type is abstract and it is used at a level that is deeper than the current level. -When you add a `Type` contextual parameter to a method you will either get it from another context parameter or implicitly with a call to `Type.of`. +When you add a `Type` contextual parameter to a method, you will either get it from another context parameter or implicitly with a call to `Type.of`: ```scala evalAndUse(Expr(3)) // is equivalent to evalAndUse[Int](Expr(3))(using Type.of[Int]) ``` -As you may have guessed, not every type is can be used in this `Type.of[..]` out of the box. -We cannot recover abstract types that have already been erased. +As you may have guessed, not every type can be used as a parameter to `Type.of[..]` out of the box. +For example, we cannot recover abstract types that have already been erased: ```scala def evalAndUse[T](x: Expr[T])(using Quotes) = given Type[T] = Type.of[T] // error @@ -124,14 +123,14 @@ def evalAndUse[T](x: Expr[T])(using Quotes) = ``` But we can write more complex types that depend on these abstract types. -For example, if we look for or construct explicitly a `Type[List[T]]`, then the system will require a `Type[T]` in the current context to compile. +For example, if we look for or explicitly construct a `Type[List[T]]`, then the system will require a `Type[T]` in the current context to compile. -Good code should only add `Type` to the context parameters and never use them explicitly. -Explicit use is useful while debugging at the cost of conciseness and clarity. +Good code should only add `Type`s to the context parameters and never use them explicitly. +However, explicit use is useful while debugging, though it comes at the cost of conciseness and clarity. ## ToExpr -The `Expr.apply` method uses intances of `ToExpr` to generate an expression that will create a copy of the value. +The `Expr.apply` method uses instances of `ToExpr` to generate an expression that will create a copy of the value. ```scala object Expr: def apply[T](x: T)(using Quotes, ToExpr[T]): Expr[T] = @@ -161,16 +160,16 @@ given ToExpr[StringContext] with { } ``` The `Varargs` constructor just creates an `Expr[Seq[T]]` which we can efficiently splice as a varargs. -In general any sequence can be spliced with `$mySeq: _*` to splice it a varargs. +In general, any sequence can be spliced with `$mySeq: _*` to splice it as a varargs. ## Quoted patterns -Quotes can also be used to check if an expression is equivalent to another or deconstruct an expression into it parts. +Quotes can also be used to check if an expression is equivalent to another or to deconstruct an expression into its parts. ### Matching exact expression -The simples thing we can do is to check if an expression matches another know expression. -Bellow we show how we can match some expressions using `case '{...} =>` +The simplest thing we can do is to check if an expression matches another known expression. +Below, we show how we can match some expressions using `case '{...} =>`. ```scala def valueOfBoolean(x: Expr[Boolean])(using Quotes): Option[Boolean] = @@ -189,7 +188,7 @@ def valueOfBooleanOption(x: Expr[Option[Boolean]])(using Quotes): Option[Option[ ### Matching partial expression -To make thing more compact, we can also match patially the expression using a `$` to match arbitrarry code and extract it. +To make things more compact, we can also match a part of the expression using a splice (`$`) to match arbitrary code and extract it. ```scala def valueOfBooleanOption(x: Expr[Option[Boolean]])(using Quotes): Option[Option[Boolean]] = @@ -201,8 +200,8 @@ def valueOfBooleanOption(x: Expr[Option[Boolean]])(using Quotes): Option[Option[ ### Matching types of expression -We can also match agains code of an arbitrary type `T`. -Bellow we match agains `$x` of type `T` and we get out an `x` of type `Expr[T]`. +We can also match against code of an arbitrary type `T`. +Below, we match against `$x` of type `T` and we get out an `x` of type `Expr[T]`. ```scala def exprOfOption[T: Type](x: Expr[Option[T]])(using Quotes): Option[Expr[T]] = @@ -212,7 +211,7 @@ def exprOfOption[T: Type](x: Expr[Option[T]])(using Quotes): Option[Expr[T]] = case _ => None ``` -We can also check for the type of an expression +We can also check for the type of an expression: ```scala def valueOf(x: Expr[Any])(using Quotes): Option[Any] = @@ -221,7 +220,7 @@ def valueOf(x: Expr[Any])(using Quotes): Option[Any] = case '{ $x: Option[Boolean] } => valueOfBooleanOption(x) // x: Expr[Option[Boolean]] case _ => None ``` -Or similarly for an some subexpression +Or similarly for a partial expression: ```scala case '{ Some($x: Boolean) } => // x: Expr[Boolean] @@ -229,15 +228,15 @@ case '{ Some($x: Boolean) } => // x: Expr[Boolean] ### Matching receiver of methods -When we want to match the receiver of a method we need to explicitly state its type +When we want to match the receiver of a method, we need to explicitly state its type: ```scala case '{ ($ls: List[Int]).sum } => ``` -If we would have written `$ls.sum` we would not have been able to know the type of `ls` and which `sum` method we are calling. +If we would have written `$ls.sum`, we would not have been able to know the type of `ls` and which `sum` method we are calling. -Another common case where we need type annotations is for infix operations. +Another common case where we need type annotations is for infix operations: ```scala case '{ ($x: Int) + ($y: Int) } => case '{ ($x: Double) + ($y: Double) } => @@ -267,9 +266,9 @@ def exprOfOption[T: Type](x: Expr[Option[T]])(using Quotes): Option[Expr[T]] = Note that this time we have added the `T` explicitly in the pattern, even though it could be inferred. By referring to the generic type `T` in the pattern, we are required to have a given `Type[T]` in scope. This implies that `$x: T` will only match if `x` is of type `Expr[T]`. -In this particular case this condition will always be true. +In this particular case, this condition will always be true. -Now consider the following variant where `x` is an optional value with a (statically) unknown element type. +Now consider the following variant where `x` is an optional value with a (statically) unknown element type: ```scala def exprOfOptionOf[T: Type](x: Expr[Option[Any]])(using Quotes): Option[Expr[T]] = @@ -277,7 +276,7 @@ def exprOfOptionOf[T: Type](x: Expr[Option[Any]])(using Quotes): Option[Expr[T]] case '{ Some($x: T) } => Some(x) // x: Expr[T] case _ => None ``` -This time the pattern ` Some($x: T)` will only match if the type of the option is `Some[T]`. +This time, the pattern `Some($x: T)` will only match if the type of the Option is `Some[T]`. ```scala exprOfOptionOf[Int]('{ Some(3) }) // Some('{3}) @@ -302,10 +301,12 @@ def exprOptionToList(x: Expr[Option[Any]])(using Quotes): Option[Expr[List[Any]] ``` The pattern `$x: t` will match an expression of any type and `t` will be bound to the type of the pattern. -This type is only valid in the right-hand side of the `case`, in the example we can use it to construct the list `List[t]($x)` (`List($x)` would also work). -As this is a type that is not statically known we need a given `Type[t]` in scope, luckily the quoted pattern will automatically provide this. +This type variable is only valid in the right-hand side of the `case`. +In this example, we use it to construct the list `List[t]($x)` (`List($x)` would also work). +As this is a type that is not statically, known we need a given `Type[t]` in scope. +Luckily, the quoted pattern will automatically provide this for us. -The simple `case '{ $expr: tpe } =>` pattern is very useful if we want to know the precise type of the expression. +The simple pattern `case '{ $expr: tpe } =>` is very useful if we want to know the precise type of the expression. ```scala val expr: Expr[Option[Int]] = ... expr match @@ -316,9 +317,13 @@ expr match ``` In some cases we need to define a pattern variable that is referenced several times or has some type bounds. -To achieve this it is possible to create pattern variables at the start of the pattern using `type t` with a type pattern variable. +To achieve this, it is possible to create pattern variables at the start of the pattern using `type t` with a type pattern variable. ```scala +/** + * Use: Converts a redundant `list.map(f).map(g)` to only use one call + * to `map`: `list.map(y => g(f(y)))`. + */ def fuseMap[T: Type](x: Expr[List[T]])(using Quotes): Expr[List[T]] = x match { case '{ type u @@ -327,21 +332,21 @@ def fuseMap[T: Type](x: Expr[List[T]])(using Quotes): Expr[List[T]] = x match { .map($f: `u` => `v`) .map($g: `v` => T) } => - '{ $ls.map(x => $g($f(x))) } + '{ $ls.map(y => $g($f(y))) } case _ => x } ``` -Here we define two type variables `u` and `v` and then refer to them using `` `u` `` and `` `v` ``. -We do not refer to them using `u` or `v` because those would be interpreted as new type variables and hence duplicates. +Here, we define two type variables `u` and `v` and then refer to them using `` `u` `` and `` `v` ``. +We do not refer to them using `u` or `v` (without backticks) because those would be interpreted as new type variables with the same variable name. This notation follows the normal [stable identifier patterns](https://www.scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#stable-identifier-patterns) syntax. -Furthermore, if the type variable needs to be constrained we can add bounds directly on the type definition `case '{ type u <: AnyRef; ... } =>`. +Furthermore, if the type variable needs to be constrained, we can add bounds directly on the type definition: `case '{ type u <: AnyRef; ... } =>`. Note that the previous case could also be written as `case '{ ($ls: List[u]).map[v]($f).map[T]($g) =>`. #### Quote types patterns -Type represented with `Type[T]` can be matched on using the patten `case '[...] =>`. +Types represented with `Type[T]` can be matched on using the patten `case '[...] =>`. ```scala def mirrorFields[T: Type](using Quotes): List[String] = @@ -358,11 +363,11 @@ mirrorFields[(Int, String, Int)] // List("Int", "String", "Int") mirrorFields[Tuple] // error: Expected known tuple but got: Tuple ``` -As with expression quote patterns type variables are represented using lower case names. +As with expression quote patterns, type variables are represented using lower case names. ## FromExpr -The `Expr.value`, `Expr.valueOrError` `Expr.unapply` method uses intances of `FromExpr` to to extract the value if possible. +The `Expr.value`, `Expr.valueOrError`, and `Expr.unapply` methods uses intances of `FromExpr` to extract the value if possible. ```scala extension [T](expr: Expr[T]): def value(using Quotes)(using fromExpr: FromExpr[T]): Option[T] = @@ -383,9 +388,9 @@ trait FromExpr[T]: def unapply(x: Expr[T])(using Quotes): Option[T] ``` -The `FromExpr.unapply` method will take a value `T` and generate code that will construct a copy of this value at runtime. +The `FromExpr.unapply` method will take a value `x` and generate code that will construct a copy of this value at runtime. -We can define our own `FromExpr`s like: +We can define our own `FromExpr`s like so: ```scala given FromExpr[Boolean] with { def unapply(x: Expr[Boolean])(using Quotes): Option[Boolean] = @@ -403,8 +408,8 @@ given FromExpr[StringContext] with { } } ``` -Note that we handled two cases for the `StringContext`. -As it is a `case class` it can be created with the `new StringContext` or with the `StringContext.apply` in the companion object. +Note that we handled two cases for `StringContext`. +As it is a `case class`, it can be created with `new StringContext` or with `StringContext.apply` from the companion object. We also used the `Varargs` extractor to match the arguments of type `Expr[Seq[String]]` into a `Seq[Expr[String]]`. Then we used the `Exprs` to match known constants in the `Seq[Expr[String]]` to get a `Seq[String]`. @@ -412,10 +417,11 @@ Then we used the `Exprs` to match known constants in the `Seq[Expr[String]]` to ## The Quotes The `Quotes` is the main entry point for the creation of all quotes. This context is usually just passed around through contextual abstractions (`using` and `?=>`). -Each quote scope will provide have its own `Quotes`. -New scopes are introduced each time a splice is introduced `${...}`. +Each quote scope will have its own `Quotes`. +New scopes are introduced each time a splice is introduced (`${ ... }`). Though it looks like a splice takes an expression as argument, it actually takes a `Quotes ?=> Expr[T]`. -Therefore we could actually write it explicitly as `${ (using q) => ... }`, this might be useful when debugging to avoid generated names for these scopes. +Therefore, we could actually write it explicitly as `${ (using q) => ... }`. +This might be useful when debugging to avoid generated names for these scopes. The method `scala.quoted.quotes` provides a simple way to use the current `Quotes` without naming it. It is usually imported along with the `Quotes` using `import scala.quoted.*`. @@ -425,9 +431,9 @@ ${ (using q1) => body(using q1) } // equivalent to ${ body(using quotes) } ``` -If you explicitly name a `Quotes` `quotes` you will shadow this definition. +Warning: If you explicitly name a `Quotes` `quotes`, you will shadow this definition. -When we write a top level splice in a macro we are calling something similar to the following definition. +When we write a top-level splice in a macro, we are calling something similar to the following definition. This splice will provide the initial `Quotes` associated with the macro expansion. ```scala def $[T](x: Quotes ?=> Expr[T]): T = ... @@ -452,12 +458,12 @@ def $[T](using q: Quotes)(x: q.Nested ?=> Expr[T]): T = ... ``` ## β-reduction -When we have a lambda applied to an argument in a quote `'{ ((x: Int) => x + x)(y) }` we do not reduce it within the quote, the code is kept as is. -There is an optimisation that β-reduce all lambdas directly applied to parameters to avoid the creation of the closure. -This will not be visible from the quotes perspective. +When we have a lambda applied to an argument in a quote `'{ ((x: Int) => x + x)(y) }`, we do not reduce it within the quote; the code is kept as-is. +There is an optimisation that will β-reduce all lambdas directly applied to parameters to avoid the creation of a closure. +This will not be visible from the quote's perspective. -Sometime it is useful to perform this β-reduction on the quotes directly. -We provide the function `Expr.betaReduce[T]` that receives an `Expr[T]` and β-reduce if it contains a directly applied lambda. +Sometimes it is useful to perform this β-reduction on the quotes directly. +We provide the function `Expr.betaReduce[T]` that receives an `Expr[T]` and β-reduces if it contains a directly-applied lambda. ```scala Expr.betaReduce('{ ((x: Int) => x + x)(y) }) // returns '{ val x = y; x + x } @@ -469,33 +475,32 @@ There are two ways to summon values in a macro. The first is to have a `using` parameter in the inline method that is passed explicitly to the macro implementation. ```scala -inline def setFor[T](using ord: Ordering[T]): Set[T] = - ${ setForCode[T]('ord) } +inline def setOf[T](using ord: Ordering[T]): Set[T] = + ${ setOfCode[T]('ord) } -def setForCode[T: Type](ord: Expr[Ordering[T]])(using Quotes): Expr[Set[T]] = +def setOfCode[T: Type](ord: Expr[Ordering[T]])(using Quotes): Expr[Set[T]] = '{ TreeSet.empty[T](using $ord) } ``` In this scenario, the context parameter is found before the macro is expanded. -If not found, the macro will not expand. +If not found, the macro will not be expanded. The second way is using `Expr.summon`. -This allows to programatically search for distinct given expressions. -The following example is similar to the previous example. +This allows us to programatically search for distinct given expressions. +The following example is similar to the previous example: ```scala -inline def setFor[T]: Set[T] = - ${ setForCode[T] } +inline def setOf[T]: Set[T] = + ${ setOfCode[T] } -def setForCode[T: Type](using Quotes): Expr[Set[T]] = - import scala.collection.immutable.* +def setOfCode[T: Type](using Quotes): Expr[Set[T]] = Expr.summon[Ordering[T]] match case Some(ord) => '{ TreeSet.empty[T](using $ord) } case _ => '{ HashSet.empty[T] } ``` -The difference is that in this scenario we do start expanding the macro before the implicit search failure and we can write arbitrary code to handle the case where it is not found. -Here we used `HashSet` and another valid implementation that does not need the `Ordering`. +The difference is that, in the second scenario, we expand the macro before the implicit search is performed. We can therefore write arbitrary code to handle the case when an `Ordering[T]` is not found. +Here, we used `HashSet` instead of `TreeSet` because the former does not need an `Ordering`. [macros]: {% link _overviews/scala3-macros/tutorial/macros.md %} [quotes]: {% link _overviews/scala3-macros/tutorial/quotes.md %} From cb5eae119ad5832d01bd536d975a1b997935f93e Mon Sep 17 00:00:00 2001 From: Zeimyth Date: Mon, 12 Jul 2021 14:43:59 -0600 Subject: [PATCH 2/4] Clean up grammar and other nit issues in reflection.md --- .../scala3-macros/tutorial/reflection.md | 96 +++++++++---------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/_overviews/scala3-macros/tutorial/reflection.md b/_overviews/scala3-macros/tutorial/reflection.md index 187c0420a6..b9bca2e1c9 100644 --- a/_overviews/scala3-macros/tutorial/reflection.md +++ b/_overviews/scala3-macros/tutorial/reflection.md @@ -7,7 +7,7 @@ previous-page: quotes --- The reflection API provides a more complex and comprehensive view on the structure of the code. -It provides a view on the *Typed Abstract Syntax Trees* and their properties such as types, symbols, positions and comments. +It provides a view of *Typed Abstract Syntax Trees* and their properties such as types, symbols, positions and comments. The API can be used in macros as well as for [inspecting TASTy files][tasty inspection]. @@ -16,8 +16,8 @@ The API can be used in macros as well as for [inspecting TASTy files][tasty insp The reflection API is defined in the type `Quotes` as `reflect`. The actual instance depends on the current scope, in which quotes or quoted pattern matching is used. Hence, every macro method receives Quotes as an additional argument. -Since `Quotes` is contextual, to access its members we either need to name the parameter, or summon it. -The following definition of the standard library provides the canonical way of accessing it. +Since `Quotes` is contextual, to access its members we either need to name the parameter or summon it. +The following definition from the standard library details the canonical way of accessing it: ```scala package scala.quoted @@ -25,10 +25,10 @@ package scala.quoted transparent inline def quotes(using inline q: Quotes): q.type = q ``` -We can use `scala.quoted.quotes` to import it the current `Quotes` in scope like this +We can use `scala.quoted.quotes` to import the current `Quotes` in scope: ```scala -import scala.quoted.* // Import `quotes`, `Quotes` and `Expr` +import scala.quoted.* // Import `quotes`, `Quotes`, and `Expr` def f(x: Expr[Int])(using Quotes): Expr[Int] = import quotes.reflect.* // Import `Tree`, `TypeRepr`, `Symbol`, `Position`, ..... @@ -41,16 +41,17 @@ This will import all the types and modules (with extension methods) of the API. ## How to navigate the API The full API can be found in the [API documentation for `scala.quoted.Quotes.reflectModule`][reflection doc]. -Unfortunately, at this stage, this automatically generated documentation is not very easy to navigate. +Unfortunately, at this stage, this automatically-generated documentation is not very easy to navigate. The most important element on the page is the hierarchy tree which provides a synthetic overview of the subtyping relationships of the types in the API. For each type `Foo` in the tree: - the trait `FooMethods` contains the methods available on the type `Foo` - - the trait `FooModule` contains the static methods available on the object `Foo`. Most notably, constructors (`apply/copy`) and the `unapply` method which provides the extractor(s) required for pattern matching. - - For all types `Upper` such that `Foo <: Upper`, the methods defined in `UpperMethods` are available on `Foo` as well. + - the trait `FooModule` contains the static methods available on the object `Foo`. +Most notably, constructors (`apply/copy`) and the `unapply` method which provides the extractor(s) required for pattern matching are found here + - For all types `Upper` such that `Foo <: Upper`, the methods defined in `UpperMethods` are also available on `Foo` -For example [`TypeBounds`](https://scala-lang.org/api/3.x/scala/quoted/Quotes$reflectModule.html#TypeBounds-0), a subtype of `TypeRepr`, represents a type tree of the form `T >: L <: U`: a type `T` which is a super type of `L` +For example, [`TypeBounds`](https://scala-lang.org/api/3.x/scala/quoted/Quotes$reflectModule.html#TypeBounds-0), a subtype of `TypeRepr`, represents a type tree of the form `T >: L <: U`: a type `T` which is a super type of `L` and a subtype of `U`. In [`TypeBoundsMethods`](https://scala-lang.org/api/3.x/scala/quoted/Quotes$reflectModule$TypeBoundsMethods.html), you will find the methods `low` and `hi`, which allow you to access the representations of `L` and `U`. In [`TypeBoundsModule`](https://scala-lang.org/api/3.x/scala/quoted/Quotes$reflectModule$TypeBoundsModule.html), you will find the `unapply` method, which allows you to write: @@ -60,8 +61,7 @@ def f(tpe: TypeRepr) = case TypeBounds(l, u) => ``` -Remember also that `TypeBounds <: TypeRepr`, therefore all the methods defined in `TypeReprMethods` are -available on `TypeBounds` values as in: +Because `TypeBounds <: TypeRepr`, all the methods defined in `TypeReprMethods` are available on `TypeBounds` values: ```scala def f(tpe: TypeRepr) = @@ -75,12 +75,12 @@ def f(tpe: TypeRepr) = ### Expr and Term -Expressions `Expr[T]` can be seen as wrappers around a `Term`, where `T` is the statically known type of the term. -Below we use the extension method `asTerm` to transform the expression into a term. +Expressions (`Expr[T]`) can be seen as wrappers around a `Term`, where `T` is the statically-known type of the term. +Below, we use the extension method `asTerm` to transform an expression into a term. This extension method is only available after importing `quotes.reflect.asTerm`. Then we use `asExprOf[Int]` to transform the term back into `Expr[Int]`. -This operation will fail if the term does not have the provided type (here `Int`) or if the term is not a valid expression. -For example, a `Ident(fn)` is non-valid term if the method `fn` takes type parameters, in which case we would need an `Apply(Ident(fn), args)`. +This operation will fail if the term does not have the provided type (in this case, `Int`) or if the term is not a valid expression. +For example, an `Ident(fn)` is an invalid term if the method `fn` takes type parameters, in which case we would need an `Apply(Ident(fn), args)`. ```scala def f(x: Expr[Int])(using Quotes): Expr[Int] = @@ -92,10 +92,10 @@ def f(x: Expr[Int])(using Quotes): Expr[Int] = ### Type and TypeRepr -Similarly, we can also see `Type[T]` as a wrapper over `TypeRepr`, with `T` being the statically known type. -To get a `TypeRepr` we use `TypeRepr.of[X]` which expects a given `Type[X]` in scope (similar to `Type.of[X]`). +Similarly, we can also see `Type[T]` as a wrapper over `TypeRepr`, with `T` being the statically-known type. +To get a `TypeRepr`, we use `TypeRepr.of[T]`, which expects a given `Type[T]` in scope (similar to `Type.of[T]`). We can also transform it back into a `Type[?]` using the `asType` method. -As the type of `Type[?]` is not statically know we need to name it with an existential type to use it. This can be achieved using a `'[t]` pattern. +As the type of `Type[?]` is not statically known, we need to name it with an existential type to use it. This can be achieved using the `'[t]` pattern. ```scala def g[T: Type](using Quotes) = @@ -108,39 +108,39 @@ def g[T: Type](using Quotes) = ## Symbols -The APIs of `Term` and `TypeRepr` are relatively *closed* in the sense that methods produce and accept values -whose types are defined in the API. You might notice however the presence of `Symbol`s which identify definitions. +The APIs of `Term` and `TypeRepr` are relatively *closed* in the sense that methods produce and accept values whose types are defined in the API. +However, you might notice the presence of `Symbol`s which identify definitions. -Both `Term` or `TypeRepr` (therefore `Expr` and `Type`) have an associated symbol. +Both `Term`s and `TypeRepr`s (and therefore `Expr`s and `Type`s) have an associated symbol. `Symbol`s make it possible to compare two definitions using `==` to know if they are the same. -In addition `Symbol` exposes and is used by many useful methods. For example: +In addition, `Symbol` exposes and is used by many useful methods. For example: - `declaredFields` and `declaredMethods` allow you to iterate on the fields and members defined inside a symbol - `flags` allows you to check multiple properties of a symbol - - `companionObject` and `companionModule` provide a way to jump to and from the companion object/class. - - `TypeRepr.baseClasses` returns the list of symbols of classes extended by a type. - - `Symbol.pos` gives you access to the position where the symbol is defined, the source code of the definition - and even the filename where the symbol is defined. - - and many useful others that you can find in [`SymbolMethods`](https://scala-lang.org/api/3.x/scala/quoted/Quotes$reflectModule$SymbolMethods.html) + - `companionObject` and `companionModule` provide a way to jump to and from the companion object/class + - `TypeRepr.baseClasses` returns the list of symbols of classes extended by a type + - `Symbol.pos` gives you access to the position where the symbol is defined, the source code of the definition, and even the filename where the symbol is defined + - many others that you can find in [`SymbolMethods`](https://scala-lang.org/api/3.x/scala/quoted/Quotes$reflectModule$SymbolMethods.html) ### To Symbol and back Consider an instance of the type `TypeRepr` named `val tpe: TypeRepr = ...`. Then: - `tpe.typeSymbol` returns the symbol of the type represented by `TypeRepr`. The recommended way to obtain a `Symbol` given a `Type[T]` is `TypeRepr.of[T].typeSymbol` - - For a singleton type, `tpe.termSymbol` returns the symbol of the underlying object or value. + - For a singleton type, `tpe.termSymbol` returns the symbol of the underlying object or value - `tpe.memberType(symbol)` returns the `TypeRepr` of the provided symbol - - On objects `t: Tree`, `t.symbol` returns the symbol associated to a tree. Given that `Term <: Tree`, - `Expr.asTerm.symbol` is the best way to obtain the symbol associated to an `Expr[T]` - - On objects `sym : Symbol`, `sym.tree` returns the `Tree` associated to the symbol. Be careful when using this - method as the tree for a symbol might not be defined. Read more on the [best practices page][best practices] + - On objects `t: Tree`, `t.symbol` returns the symbol associated with a tree. + Given that `Term <: Tree`, `Expr.asTerm.symbol` is the best way to obtain the symbol associated with an `Expr[T]` + - On objects `sym: Symbol`, `sym.tree` returns the `Tree` associated to the symbol. +Be careful when using this method as the tree for a symbol might not be defined. +Read more on the [best practices page][best practices] ## Macro API design -It will be often useful to create helper methods or extractors that perform some common logic of your macros. +It will often be useful to create helper methods or extractors that perform some common logic of your macros. The simplest methods will be those that only mention `Expr`, `Type`, and `Quotes` in their signature. -Internally they may use reflection but this will not be seen at the use site of the method. +Internally, they may use reflection, but this will not be seen at the use site of the method. ```scala def f(x: Expr[Int])(using Quotes): Expr[Int] = @@ -148,8 +148,8 @@ def f(x: Expr[Int])(using Quotes): Expr[Int] = ... ``` -In some cases it is inevitable to require some methods that work on `Tree`s or other types in `quotes.reflect`. -For these cases, the best is to follow the following example of method signatures. +In some cases, it may be inevitable that some methods will expect or return `Tree`s or other types in `quotes.reflect`. +For these cases, the best practice is to follow the following method signature examples: A method that takes a `quotes.reflect.Term` parameter ```scala @@ -164,7 +164,7 @@ extension (using Quotes)(term: quotes.reflect.Term) def g: quotes.reflect.Tree = ... ``` -An extractor that matches on `quotes.reflect.Term`s. +An extractor that matches on `quotes.reflect.Term`s ```scala object MyExtractor: def unapply(using Quotes)(x: quotes.reflect.Term) = @@ -173,32 +173,32 @@ object MyExtractor: ``` > **Avoid saving the `Quotes` context in a field.** -> `Quotes` in fields inevitably make its use harder by hitting errors involving `Quotes` with different paths. +> `Quotes` in fields inevitably make its use harder by causing errors involving `Quotes` with different paths. > -> Usually these patterns have been seen in code that uses the Scala 2 ways to define extension methods or contextual unapplies. +> Usually, these patterns have been seen in code that uses the Scala 2 ways to define extension methods or contextual unapplies. > Now that we have `given` parameters that can be added before other parameters, all these old workarounds are not needed anymore. -> The new abstraction make it simpler on the definition site and at use site. +> The new abstractions make it simpler both at the definition site and at the use site. ## Debugging ### Runtime checks -Expressions `Expr[T]` can be seen as wrappers around a `Term`, where `T` is the statically known type of the term. -Hence these checks will be done at runtime (i.e. compile-time when the macro expands). +Expressions (`Expr[T]`) can be seen as wrappers around a `Term`, where `T` is the statically-known type of the term. +Hence, these checks will be done at runtime (i.e. compile-time when the macro expands). It is recommended to enable the `-Xcheck-macros` flag while developing macros or on the tests for the macro. This flag will enable extra runtime checks that will try to find ill-formed trees or types as soon as they are created. There is also the `-Ycheck:all` flag that checks all compiler invariants for tree well-formedness. -These check will usually fail with an assertion error. +These checks will usually fail with an assertion error. ### Printing the trees -The `toString` methods of types in `quotes.reflect` are not great for debugging as they show the internal representation rather than the `quotes.reflect` representation. -In many cases these are similar but they may lead the debugging process astray. +The `toString` methods on types in the `quotes.reflect` package are not great for debugging as they show the internal representation rather than the `quotes.reflect` representation. +In many cases these are similar, but they may sometimes lead the debugging process astray, so they shouldn't be relied on. -Hence, `quotes.reflect.Printers` provide a set of useful printers for debugging. -Notably the `TreeStructure`, `TypeReprStructure` and `ConstantStructure` can be quite useful. +Instead, `quotes.reflect.Printers` provides a set of useful printers for debugging. +Notably the `TreeStructure`, `TypeReprStructure`, and `ConstantStructure` classes can be quite useful. These will print the tree structure following loosely the extractors that would be needed to match it. ```scala @@ -218,7 +218,7 @@ tree match ``` This way, if a case is missed the error will report a familiar structure that can be copy-pasted to start fixing the issue. -We can make this printer the default if needed +You can make this printer the default if desired: ```scala import quotes.reflect.* given Printer[Tree] = Printer.TreeStructure From 6b1565a4a48f82480916a2ac5a50e96fa1ed7017 Mon Sep 17 00:00:00 2001 From: Zeimyth Date: Mon, 12 Jul 2021 15:02:46 -0600 Subject: [PATCH 3/4] Clean up grammar and other nit issues in best-practices.md --- _overviews/scala3-macros/best-practices.md | 45 ++++++++++------------ 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/_overviews/scala3-macros/best-practices.md b/_overviews/scala3-macros/best-practices.md index 8c7413beaf..1122c98620 100644 --- a/_overviews/scala3-macros/best-practices.md +++ b/_overviews/scala3-macros/best-practices.md @@ -6,7 +6,7 @@ num: 8 ## Inline ### Be careful when inlining for performance -To take the most advantage of the JVM JIT optimisations you want to avoid generating large methods. +To take the most advantage of the JVM JIT optimisations, you want to avoid generating large methods. ## Macros @@ -16,24 +16,24 @@ To take the most advantage of the JVM JIT optimisations you want to avoid genera ## Quoted code ### Keep quotes readable -* Try to avoid `${..}` with arbitrary expressions inside +* Try to avoid `${...}` with arbitrary expressions inside * Use `$someExpr` * Use `${ someExprFrom('localExpr) }` To illustrate, consider the following example: ```scala -val x: StringContext = ... -'{ StringContext(${Varargs(stringContext.parts.map(Expr(_)))}: _*) } +val sc: StringContext = ... +'{ StringContext(${Varargs(sc.parts.map(Expr(_)))}: _*) } ``` -Instead we can write the following: +Instead, we can write the following: ```scala -val x: StringContext = ... -val partExprs = stringContext.parts.map(Expr(_)) +val sc: StringContext = ... +val partExprs = sc.parts.map(Expr(_)) val partsExpr = Varargs(partExprs) '{ StringContext($partsExpr: _*) } ``` -The contents of the quote are cleared this way. +The contents of the quote are much more clear in the second example. ### Avoid nested contexts @@ -74,34 +74,29 @@ val leafSym: Symbol = leafTpe.typeSymbol ### Avoid `Symbol.tree` -On an object `sym: Symbol`, `sym.tree` returns the `Tree` associated to the -symbol. Be careful when using this method as the tree for a symbol might not be -defined. When the code associated to the symbol is defined in a different -moment than this access, if the `-Yretain-trees` compilation option is not -used, then the `tree` of the symbol will not be available. Symbols originated -from Java code do not have an associated `tree`. +On an object `sym: Symbol`, `sym.tree` returns the `Tree` associated with the symbol. +Be careful when using this method, as the tree for a symbol might not be defined. +When the code associated with a symbol is defined at a different time than this access, if the `-Yretain-trees` compilation option is not used, then the `tree` of the symbol will not be available. +Symbols originating from Java code do not have an associated `tree`. ### Obtaining a `TypeRepr` from a `Symbol` -In the previous paragraph we saw that `Symbol.tree` should be avoided and -therefore you should not use `sym.tree.tpe` on `sym: Symbol`. Thus to obtain -the `TypeRepr` corresponding to a `Symbol`, it is recommended to use -`tpe.memberType` on objects `tpe: TypeRepr`. +In the previous heading, we saw that `Symbol.tree` should be avoided and that therefore you should not use `sym.tree.tpe` on `sym: Symbol`. +Thus, to obtain the `TypeRepr` corresponding to a `Symbol`, it is recommended to use `tpe.memberType` on `tpe: TypeRepr` objects. We can obtain the `TypeRepr` of `Leaf` in two ways: 1. `TypeRepr.of[Box.Leaf]` - 2. `boxTpe.memberType(leafSym)`, in other words we request the `TypeRepr` of - the member of `Box` whose symbol is equal to the symbol of sym + 2. `boxTpe.memberType(leafSym)` +(In other words, we request the `TypeRepr` of the member of `Box` whose symbol is equal to the symbol of `leafSym`.) -while the two approaches are equivalent, the first is possible only if you -already know that you are looking for `Box.Leaf`. The second approach allows -you to explore an uknown API. +While the two approaches are equivalent, the first is only possible if you already know that you are looking for the type `Box.Leaf`. +The second approach allows you to explore an unknown API. ### Use `Symbol`s to compare definitions Read more about Symbols [here][symbol]. -Symbols allow comparing definitions using `==`: +Symbols allow you to compare definitions using `==`: ```scala leafSym == baseSym.children.head // Is true ``` @@ -113,7 +108,7 @@ boxTpe.memberType(baseSym.children.head) == leafTpe // Is false ### Obtaining a Symbol for a type -There is a handy shortcut to get the symbol of the definition of `T`. +There is a handy shortcut to get the symbol for the definition of `T`. Instead of ```scala From 665c8266563325ce3345abe7b255b1bb2a41ce2b Mon Sep 17 00:00:00 2001 From: Zeimyth Date: Tue, 13 Jul 2021 16:08:13 -0600 Subject: [PATCH 4/4] Clean up grammar and other nit issues in macros.md --- _overviews/scala3-macros/tutorial/macros.md | 10 +++++----- _overviews/scala3-macros/tutorial/quotes.md | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/_overviews/scala3-macros/tutorial/macros.md b/_overviews/scala3-macros/tutorial/macros.md index 269ef8876e..81cbc920d1 100644 --- a/_overviews/scala3-macros/tutorial/macros.md +++ b/_overviews/scala3-macros/tutorial/macros.md @@ -73,7 +73,7 @@ As a technicaly consequence, we cannot define and use a macro in the **same clas However, it is possible to have the macro definition and its call in the **same project** as long as the implementation of the macro can be compiled first. > ##### Suspended Files -> To allow defining and using macros in the same project, only those calls to macros are expanded, where the macro has already been compiled. +> To allow defining and using macros in the same project, only those calls to macros that have already been compiled are expanded. > For all other (unknown) macro calls, the compilation of the file is _suspended_. > Suspended files are only compiled after all non suspended files have been successfully compiled. > In some cases, you will have _cyclic dependencies_ that will block the completion of the compilation. @@ -206,7 +206,7 @@ def sumCode(nums: Expr[Seq[Int]])(using Quotes): Expr[Int] = The extractor will match a call to `sumNow(1, 2, 3)` and extract a `Seq[Expr[Int]]` containing the code of each parameter. But, if we try to match the argument of the call `sumNow(nums: _*)`, the extractor will not match. -`Varargs` can also be used as a constructor, `Varargs(Expr(1), Expr(2), Expr(3))` will return a `Expr[Seq[Int]]`. +`Varargs` can also be used as a constructor. `Varargs(Expr(1), Expr(2), Expr(3))` will return an `Expr[Seq[Int]]`. We will see how this can be useful later. @@ -226,8 +226,8 @@ while subsequent chapters introduce the more advanced APIs. ### Collections We have seen how to convert a `List[Int]` into an `Expr[List[Int]]` using `Expr.apply`. -How about converting a `List[Expr[Int]]` into `Expr[List[Int]]`? -We mentioned that `Varargs.apply` can do this for sequences -- likewise for other collection types, corresponding methods are available: +How about converting a `List[Expr[Int]]` into an `Expr[List[Int]]`? +We mentioned that `Varargs.apply` can do this for sequences; likewise, for other collection types, corresponding methods are available: * `Expr.ofList`: Transform a `List[Expr[T]]` into `Expr[List[T]]` * `Expr.ofSeq`: Transform a `Seq[Expr[T]]` into `Expr[Seq[T]]` (just like `Varargs`) @@ -269,7 +269,7 @@ Note, that `matches` only performs a limited amount of normalization and while f ### Arbitrary Expressions Last but not least, it is possible to create an `Expr[T]` from arbitary Scala code by enclosing it in [quotes][quotes]. -For example `'{ ${expr}; true }` will generate an `Expr[Int]` equivalent to `Expr.block(List(expr), Expr(true))`. +For example, `'{ ${expr}; true }` will generate an `Expr[Int]` equivalent to `Expr.block(List(expr), Expr(true))`. The subsequent section on [Quoted Code][quotes] presents quotes in more detail. [contributing]: {% link scala3/contribute-to-docs.md %} diff --git a/_overviews/scala3-macros/tutorial/quotes.md b/_overviews/scala3-macros/tutorial/quotes.md index 0c4ef57d16..b7d7c827b3 100644 --- a/_overviews/scala3-macros/tutorial/quotes.md +++ b/_overviews/scala3-macros/tutorial/quotes.md @@ -20,7 +20,7 @@ println(printHello.show) // print("Hello") In general, the quote delays the execution while the splice makes it happen before the surrounding code. This generalisation allows us to also give meaning to a `${ ... }` that is not within a quote. This evaluates the code within the splice at compile-time and places the result in the generated code. -Due to some technical considerations, only non-quoted splices are allowed directly within `inline` definitions that we call a [macro][macros]. +Due to some technical considerations, only top-level splices are allowed directly within `inline` definitions that we call a [macro][macros]. It is possible to write a quote within a quote, but this pattern is not common when writing macros. @@ -103,7 +103,7 @@ The less verbose version is usually the best way to write the types as it is muc In some cases, we will not statically know the type within the `Type` and will need to use the `t.Underlying` to refer to it. When do we need this extra `Type` parameter? -* When a type is abstract and it is used at a level that is deeper than the current level. +* When a type is abstract and it is used at a level that is higher than the current level. When you add a `Type` contextual parameter to a method, you will either get it from another context parameter or implicitly with a call to `Type.of`: ```scala @@ -276,7 +276,7 @@ def exprOfOptionOf[T: Type](x: Expr[Option[Any]])(using Quotes): Option[Expr[T]] case '{ Some($x: T) } => Some(x) // x: Expr[T] case _ => None ``` -This time, the pattern `Some($x: T)` will only match if the type of the Option is `Some[T]`. +This time, the pattern `Some($x: T)` will only match if the type of the `Option` is `Some[T]`. ```scala exprOfOptionOf[Int]('{ Some(3) }) // Some('{3})