Skip to content

Commit 0414061

Browse files
authored
Merge branch 'scala:main' into _overview/scala3-book/num24-code-tabs
2 parents d6aa71d + 63f25ac commit 0414061

File tree

5 files changed

+138
-32
lines changed

5 files changed

+138
-32
lines changed

_overviews/scala3-macros/faq.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ All quotes containing a value of a primitive type is optimised to an `Expr.apply
1313
Choose one in your project and stick with a single notation to avoid confusion.
1414

1515
## How do I get a value out of an `Expr`?
16-
If the expression represents a value, you can use `.value`, `.valueOrError` or `Expr.unapply`
16+
If the expression represents a value, you can use `.value`, `.valueOrAbort` or `Expr.unapply`
1717

1818
## How can I get the precise type of an `Expr`?
1919
We can get the precise type (`Type`) of an `Expr` using the following pattern match:

_overviews/scala3-macros/tutorial/macros.md

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@ Macros enable us to do exactly this: treat **programs as data** and manipulate t
1414

1515
## Macros Treat Programs as Values
1616
With a macro, we can treat programs as values, which allows us to analyze and generate them at compile time.
17+
1718
A Scala expression with type `T` is represented by an instance of the type `scala.quoted.Expr[T]`.
1819

1920
We will dig into the details of the type `Expr[T]`, as well as the different ways of analyzing and constructing instances, when talking about [Quoted Code][quotes] and [Reflection][tasty].
2021
For now, it suffices to know that macros are metaprograms that manipulate expressions of type `Expr[T]`.
2122

22-
The following macro implementation simply prints the expression of the provided argument:
23+
The following macro implementation prints the expression of the provided argument:
2324
```scala
25+
import scala.quoted.* // imports Quotes, Expr
26+
2427
def inspectCode(x: Expr[Any])(using Quotes): Expr[Any] =
2528
println(x.show)
2629
x
@@ -113,7 +116,7 @@ def powerCode(
113116
x: Expr[Double],
114117
n: Expr[Int]
115118
)(using Quotes): Expr[Double] =
116-
val value: Double = pow(x.valueOrError, n.valueOrError)
119+
val value: Double = pow(x.valueOrAbort, n.valueOrAbort)
117120
Expr(value)
118121
```
119122
Here, the `pow` operation is a simple Scala function that computes the value of `xⁿ`.
@@ -131,22 +134,36 @@ Other types can also work if a `ToExpr` is implemented for it, we will [see this
131134

132135
### Extracting Values from Expressions
133136

134-
The second method we use in the implementation of `powerCode` is `Expr[T].valueOrError`, which has an effect opposite to `Expr.apply`.
137+
The second method we use in the implementation of `powerCode` is `Expr[T].valueOrAbort`, which has an effect opposite to `Expr.apply`.
135138
It attempts to extract a value of type `T` from an expression of type `Expr[T]`.
136139
This can only succeed, if the expression directly contains the code of a value, otherwise, it will throw an exception that stops the macro expansion and reports that the expression did not correspond to a value.
137140

138-
Instead of `valueOrError`, we could also use the `value` operation, which will return an `Option`.
141+
Instead of `valueOrAbort`, we could also use the `value` operation, which will return an `Option`.
139142
This way we can report the error with a custom error message.
140143

144+
#### Reporting Custom Error Messages
145+
146+
The contextual `Quotes` parameter provides a `report` object that we can use to report a custom error message.
147+
Within a macro implementation method, you can access the contextual `Quotes` parameter with the `quotes` method
148+
(imported with `import scala.quoted.*`), then import the `report` object by `import quotes.reflect.report`.
149+
150+
#### Providing the Custom Error
151+
152+
We will provide the custom error message by calling `errorAndAbort` on the `report` object as follows:
141153
```scala
142-
...
154+
def powerCode(
155+
x: Expr[Double],
156+
n: Expr[Int]
157+
)(using Quotes): Expr[Double] =
158+
import quotes.reflect.report
143159
(x.value, n.value) match
144160
case (Some(base), Some(exponent)) =>
145-
pow(base, exponent)
161+
val value: Double = pow(base, exponent)
162+
Expr(value)
146163
case (Some(_), _) =>
147-
report.error("Expected a known value for the exponent, but was " + n.show, n)
164+
report.errorAndAbort("Expected a known value for the exponent, but was " + n.show, n)
148165
case _ =>
149-
report.error("Expected a known value for the base, but was " + x.show, x)
166+
report.errorAndAbort("Expected a known value for the base, but was " + x.show, x)
150167
```
151168

152169
Alternatively, we can also use the `Expr.unapply` extractor
@@ -155,27 +172,30 @@ Alternatively, we can also use the `Expr.unapply` extractor
155172
...
156173
(x, n) match
157174
case (Expr(base), Expr(exponent)) =>
158-
pow(base, exponent)
175+
val value: Double = pow(base, exponent)
176+
Expr(value)
159177
case (Expr(_), _) => ...
160178
case _ => ...
161179
```
162-
The operations `value`, `valueOrError`, and `Expr.unapply` will work for all _primitive types_, _tuples_ of any arity, `Option`, `Seq`, `Set`, `Map`, `Either` and `StringContext`.
180+
The operations `value`, `valueOrAbort`, and `Expr.unapply` will work for all _primitive types_, _tuples_ of any arity, `Option`, `Seq`, `Set`, `Map`, `Either` and `StringContext`.
163181
Other types can also work if an `FromExpr` is implemented for it, we will [see this later][quotes].
164182

165183

166184
### Showing Expressions
167185

168186
In the implementation of `inspectCode`, we have already seen how to convert expressions to the string representation of their _source code_ using the `.show` method.
169187
This can be useful to perform debugging on macro implementations:
188+
189+
<!-- The below code example does not use multi-line string because it causes syntax highlighting to break -->
170190
```scala
171191
def debugPowerCode(
172192
x: Expr[Double],
173193
n: Expr[Int]
174194
)(using Quotes): Expr[Double] =
175195
println(
176-
s"""powerCode
177-
| x := ${x.show}
178-
| n := ${n.show}""".stripMargin)
196+
s"powerCode \n" +
197+
s" x := ${x.show}\n" +
198+
s" n := ${n.show}")
179199
val code = powerCode(x, n)
180200
println(s" code := ${code.show}")
181201
code
@@ -188,23 +208,24 @@ Varargs in Scala are represented with `Seq`, hence when we write a macro with a
188208
It is possible to recover each individual argument (of type `Expr[T]`) using the `scala.quoted.Varargs` extractor.
189209

190210
```scala
191-
import scala.quoted.Varargs
211+
import scala.quoted.* // imports `Varargs`, `Quotes`, etc.
192212

193213
inline def sumNow(inline nums: Int*): Int =
194214
${ sumCode('nums) }
195215

196216
def sumCode(nums: Expr[Seq[Int]])(using Quotes): Expr[Int] =
217+
import quotes.reflect.report
197218
nums match
198219
case Varargs(numberExprs) => // numberExprs: Seq[Expr[Int]]
199-
val numbers: Seq[Int] = numberExprs.map(_.valueOrError)
220+
val numbers: Seq[Int] = numberExprs.map(_.valueOrAbort)
200221
Expr(numbers.sum)
201-
case _ => report.error(
202-
"Expected explicit argument" +
203-
"Notation `args: _*` is not supported.", numbersExpr)
222+
case _ => report.errorAndAbort(
223+
"Expected explicit varargs sequence. " +
224+
"Notation `args*` is not supported.", nums)
204225
```
205226

206227
The extractor will match a call to `sumNow(1, 2, 3)` and extract a `Seq[Expr[Int]]` containing the code of each parameter.
207-
But, if we try to match the argument of the call `sumNow(nums: _*)`, the extractor will not match.
228+
But, if we try to match the argument of the call `sumNow(nums*)`, the extractor will not match.
208229

209230
`Varargs` can also be used as a constructor. `Varargs(Expr(1), Expr(2), Expr(3))` will return an `Expr[Seq[Int]]`.
210231
We will see how this can be useful later.
@@ -244,7 +265,7 @@ inline def test(inline ignore: Boolean, computation: => Unit): Boolean =
244265
${ testCode('ignore, 'computation) }
245266

246267
def testCode(ignore: Expr[Boolean], computation: Expr[Unit])(using Quotes) =
247-
if ignore.valueOrError then Expr(false)
268+
if ignore.valueOrAbort then Expr(false)
248269
else Expr.block(List(computation), Expr(true))
249270
```
250271

_overviews/scala3-macros/tutorial/quotes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ As with expression quote patterns, type variables are represented using lower ca
367367

368368
## FromExpr
369369

370-
The `Expr.value`, `Expr.valueOrError`, and `Expr.unapply` methods uses intances of `FromExpr` to extract the value if possible.
370+
The `Expr.value`, `Expr.valueOrAbort`, and `Expr.unapply` methods uses intances of `FromExpr` to extract the value if possible.
371371
```scala
372372
extension [T](expr: Expr[T]):
373373
def value(using Quotes)(using fromExpr: FromExpr[T]): Option[T] =

_tour/package-objects.md

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,50 @@
11
---
22
layout: tour
3-
title: Package Objects
3+
title: Top Level Definitions in Packages
44
partof: scala-tour
55

66
num: 36
77
previous-page: packages-and-imports
88
---
99

10-
# Package objects
10+
Often, it is convenient to have definitions accessible accross an entire package, and not need to invent a
11+
name for a wrapper `object` to contain them.
1112

12-
Scala provides package objects as a convenient container shared across an entire package.
13+
{% tabs pkg-obj-vs-top-lvl_1 class=tabs-scala-version %}
14+
{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_1 %}
15+
Scala 2 provides _package objects_ as a convenient container shared across an entire package.
1316

1417
Package objects
1518
can contain arbitrary definitions, not just variable and method definitions. For instance, they are frequently
1619
used to hold package-wide type aliases and implicit conversions. Package objects can even inherit
1720
Scala classes and traits.
1821

22+
> In a future version of Scala 3, package objects will be removed in favor of top level definitions.
23+
1924
By convention, the source code for a package object is usually put in a source file named `package.scala`.
2025

2126
Each package is allowed to have one package object. Any definitions placed in a package object are considered
2227
members of the package itself.
2328

29+
{% endtab %}
30+
{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_1 %}
31+
In Scala 3, any kind of definition can be declared at the top level of a package. For example, classes, enums,
32+
methods and variables.
33+
34+
Any definitions placed at the top level of a package are considered members of the package itself.
35+
36+
> In Scala 2 top-level method, type and variable definitions had to be wrapped in a **package object**.
37+
> These are still usable in Scala 3 for backwards compatibility. You can see how they work by switching tabs.
38+
39+
{% endtab %}
40+
{% endtabs %}
41+
2442
See example below. Assume first a class `Fruit` and three `Fruit` objects in a package
2543
`gardening.fruits`:
2644

45+
46+
{% tabs pkg-obj-vs-top-lvl_2 %}
47+
{% tab 'Scala 2 and 3' for=pkg-obj-vs-top-lvl_2 %}
2748
```
2849
// in file gardening/fruits/Fruit.scala
2950
package gardening.fruits
@@ -33,10 +54,15 @@ object Apple extends Fruit("Apple", "green")
3354
object Plum extends Fruit("Plum", "blue")
3455
object Banana extends Fruit("Banana", "yellow")
3556
```
57+
{% endtab %}
58+
{% endtabs %}
3659

3760
Now assume you want to place a variable `planted` and a method `showFruit` directly into package `gardening.fruits`.
3861
Here's how this is done:
3962

63+
{% tabs pkg-obj-vs-top-lvl_3 class=tabs-scala-version %}
64+
{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_3 %}
65+
4066
```
4167
// in file gardening/fruits/package.scala
4268
package gardening
@@ -47,13 +73,29 @@ package object fruits {
4773
}
4874
}
4975
```
76+
{% endtab %}
77+
{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_3 %}
78+
79+
```
80+
// in file gardening/fruits/package.scala
81+
package gardening.fruits
82+
83+
val planted = List(Apple, Plum, Banana)
84+
def showFruit(fruit: Fruit): Unit =
85+
println(s"${fruit.name}s are ${fruit.color}")
86+
```
87+
{% endtab %}
88+
{% endtabs %}
5089

51-
As an example of how to use this, the following object `PrintPlanted` imports `planted` and `showFruit` in exactly the same
52-
way it imports class `Fruit`, using a wildcard import on package gardening.fruits:
90+
As an example of how to use this, the following program imports `planted` and `showFruit` in exactly the same
91+
way it imports class `Fruit`, using a wildcard import on package `gardening.fruits`:
5392

93+
{% tabs pkg-obj-vs-top-lvl_4 class=tabs-scala-version %}
94+
{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_4 %}
5495
```
5596
// in file PrintPlanted.scala
5697
import gardening.fruits._
98+
5799
object PrintPlanted {
58100
def main(args: Array[String]): Unit = {
59101
for (fruit <- planted) {
@@ -62,11 +104,53 @@ object PrintPlanted {
62104
}
63105
}
64106
```
107+
{% endtab %}
108+
{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_4 %}
109+
```
110+
// in file printPlanted.scala
111+
import gardening.fruits.*
65112
66-
Package objects are like other objects, which means you can use inheritance for building them. For example, one might mix in a couple of traits:
113+
@main def printPlanted(): Unit =
114+
for fruit <- planted do
115+
showFruit(fruit)
116+
```
117+
{% endtab %}
118+
{% endtabs %}
119+
120+
### Aggregating Several Definitions at the Package Level
121+
122+
Often, your project may have several reusable definitions defined in various modules, that you
123+
wish to aggregate at the top level of a package.
124+
125+
For example, some helper methods in the trait `FruitHelpers` and
126+
some term/type aliases in trait `FruitAliases`. Here is how you can put all their definitions at the level of the `fruit`
127+
package:
128+
129+
{% tabs pkg-obj-vs-top-lvl_5 class=tabs-scala-version %}
130+
{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_5 %}
131+
132+
Package objects are like other objects, which means you can use inheritance for building them.
133+
So here we mix in the helper traits as parents of the package object.
67134

68135
```
69-
package object fruits extends FruitAliases with FruitHelpers {
70-
// helpers and variables follows here
71-
}
136+
package gardening
137+
138+
// `fruits` instead inherits its members from its parents.
139+
package object fruits extends FruitAliases with FruitHelpers
140+
```
141+
{% endtab %}
142+
{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_5 %}
143+
144+
In Scala 3, it is preferred to use `export` to compose members from several objects into a single scope.
145+
Here we define private objects that mix in the helper traits, then export their members at the top level:
146+
147+
```
148+
package gardening.fruits
149+
150+
private object FruitAliases extends FruitAliases
151+
private object FruitHelpers extends FruitHelpers
152+
153+
export FruitHelpers.*, FruitAliases.*
72154
```
155+
{% endtab %}
156+
{% endtabs %}

_tour/packages-and-imports.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ One convention is to name the package the same as the directory containing the S
3838
UserPreferences.scala
3939
- test
4040
```
41-
Notice how the `users` directory is within the `scala` directory and how there are multiple Scala files within the package. Each Scala file in the package could have the same package declaration. The other way to declare packages is by using braces:
41+
Notice how the `users` directory is within the `scala` directory and how there are multiple Scala files within the package. Each Scala file in the package could have the same package declaration. The other way to declare packages is by nesting them inside each other:
4242

4343
{% tabs packages-and-imports_2 class=tabs-scala-version %}
4444
{% tab 'Scala 2' for=packages-and-imports_2 %}
@@ -58,6 +58,7 @@ package users {
5858
package users:
5959
package administrators:
6060
class NormalUser
61+
6162
package normalusers:
6263
class NormalUser
6364
```

0 commit comments

Comments
 (0)