From c207fa85cd010fe92a3a478b947959c18db83080 Mon Sep 17 00:00:00 2001 From: "Lan, Jian" Date: Sat, 26 Dec 2020 01:22:22 -0800 Subject: [PATCH 01/10] Replace the implicitly with summon in the code example for NotGiven --- docs/docs/reference/contextual/givens.md | 27 ++++++++++++++++++------ 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/docs/docs/reference/contextual/givens.md b/docs/docs/reference/contextual/givens.md index 91e494d86aea..738a2d3e0f29 100644 --- a/docs/docs/reference/contextual/givens.md +++ b/docs/docs/reference/contextual/givens.md @@ -27,6 +27,7 @@ given listOrd[T](using ord: Ord[T]): Ord[List[T]] with if fst != 0 then fst else compare(xs1, ys1) ``` + This code defines a trait `Ord` with two given instances. `intOrd` defines a given for the type `Ord[Int]` whereas `listOrd[T]` defines givens for `Ord[List[T]]` for all types `T` that come with a given instance for `Ord[T]` @@ -39,20 +40,24 @@ parameters](./using-clauses.md). The name of a given can be left out. So the definitions of the last section can also be expressed like this: + ```scala given Ord[Int] with ... given [T](using Ord[T]): Ord[List[T]] with ... ``` + If the name of a given is missing, the compiler will synthesize a name from the implemented type(s). **Note** The name synthesized by the compiler is chosen to be readable and reasonably concise. For instance, the two instances above would get the names: + ```scala given_Ord_Int given_Ord_List_T ``` + The precise rules for synthesizing names are found [here](./relationship-implicits.html#anonymous-given-instances). These rules do not guarantee absence of name conflicts between given instances of types that are "too similar". To avoid conflicts one can use named instances. @@ -62,15 +67,18 @@ use named instances. ## Alias Givens An alias can be used to define a given instance that is equal to some expression. E.g.: + ```scala given global: ExecutionContext = ForkJoinPool() ``` + This creates a given `global` of type `ExecutionContext` that resolves to the right hand side `ForkJoinPool()`. The first time `global` is accessed, a new `ForkJoinPool` is created, which is then returned for this and all subsequent accesses to `global`. This operation is thread-safe. Alias givens can be anonymous as well, e.g. + ```scala given Position = enclosingTree.position given (using config: Config): Factory = MemoizingFactory(config) @@ -83,11 +91,13 @@ but it can only implement a single type. Given aliases can have the `inline` and `transparent` modifiers. Example: + ```scala transparent inline given mkAnnotations[A, T]: Annotations[A, T] = ${ // code producing a value of a subtype of Annotations } ``` + Since `mkAnnotations` is `transparent`, the type of an application is the type of its right hand side, which can be a proper subtype of the declared result type `Annotations[A, T]`. ## Pattern-Bound Given Instances @@ -100,6 +110,7 @@ for given Context <- applicationContexts do pair match case (ctx @ given Context, y) => ... ``` + In the first fragment above, anonymous given instances for class `Context` are established by enumerating over `applicationContexts`. In the second fragment, a given `Context` instance named `ctx` is established by matching against the first half of the `pair` selector. @@ -107,7 +118,9 @@ In each case, a pattern-bound given instance consists of `given` and a type `T`. ## Negated Givens -Scala 2's somewhat puzzling behavior with respect to ambiguity has been exploited to implement the analogue of a "negated" search in implicit resolution, where a query Q1 fails if some other query Q2 succeeds and Q1 succeeds if Q2 fails. With the new cleaned up behavior these techniques no longer work. But the new special type `scala.util.NotGiven` now implements negation directly. +Scala 2's somewhat puzzling behavior with respect to ambiguity has been exploited to implement the analogue of a "negated" search in implicit resolution, +where a query Q1 fails if some other query Q2 succeeds and Q1 succeeds if Q2 fails. With the new cleaned up behavior these techniques no longer work. +But the new special type `scala.util.NotGiven` now implements negation directly. For any query type `Q`, `NotGiven[Q]` succeeds if and only if the implicit search for `Q` fails, for example: @@ -124,8 +137,8 @@ object Foo: @main def test() = given Tagged[Int] with {} - assert(implicitly[Foo[Int]].value) // fooTagged is found - assert(!implicitly[Foo[String]].value) // fooNotTagged is found + assert(summon[Foo[Int]].value) // fooTagged is found + assert(!summon[Foo[String]].value) // fooNotTagged is found ``` ## Given Instance Initialization @@ -152,7 +165,7 @@ A given instance starts with the reserved word `given` and an optional _signatur defines a name and/or parameters for the instance. It is followed by `:`. There are three kinds of given instances: - - A _structural instance_ contains one or more types or constructor applications, followed by `with` and a template body -that contains member definitions of the instance. - - An _alias instance_ contains a type, followed by `=` and a right hand side expression. - - An _abstract instance_ contains just the type, which is not followed by anything. +- A _structural instance_ contains one or more types or constructor applications, + followed by `with` and a template body that contains member definitions of the instance. +- An _alias instance_ contains a type, followed by `=` and a right hand side expression. +- An _abstract instance_ contains just the type, which is not followed by anything. From d2de7fc1137ecb97e7696af8c18f6c0f66fd11a3 Mon Sep 17 00:00:00 2001 From: "Lan, Jian" Date: Sat, 26 Dec 2020 01:21:36 -0800 Subject: [PATCH 02/10] Fix a typo in derivation.md --- docs/docs/reference/contextual/derivation.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/docs/reference/contextual/derivation.md b/docs/docs/reference/contextual/derivation.md index 366f208ee6ca..95137e9207be 100644 --- a/docs/docs/reference/contextual/derivation.md +++ b/docs/docs/reference/contextual/derivation.md @@ -123,7 +123,7 @@ Note the following properties of `Mirror` types, + Properties are encoded using types rather than terms. This means that they have no runtime footprint unless used and also that they are a compile time feature for use with Scala 3's metaprogramming facilities. + The kinds of `MirroredType` and `MirroredElemTypes` match the kind of the data type the mirror is an instance for. - This allows `Mirrors` to support ADTs of all kinds. + This allows `Mirror`s to support ADTs of all kinds. + There is no distinct representation type for sums or products (ie. there is no `HList` or `Coproduct` type as in Scala 2 versions of Shapeless). Instead the collection of child types of a data type is represented by an ordinary, possibly parameterized, tuple type. Scala 3's metaprogramming facilities can be used to work with these tuple types @@ -167,7 +167,6 @@ The low-level method we will use to implement a type class `derived` method in t type-level constructs in Scala 3: inline methods, inline matches, and implicit searches via `summonInline` or `summonFrom`. Given this definition of the `Eq` type class, - ```scala trait Eq[T]: def eqv(x: T, y: T): Boolean @@ -194,7 +193,6 @@ implementation of `summonAll` is `inline` and uses Scala 3's `summonInline` cons `List`, ```scala - inline def summonAll[T <: Tuple]: List[Eq[_]] = inline erasedValue[T] match case _: EmptyTuple => Nil @@ -357,10 +355,13 @@ ConstrApps ::= ConstrApp {‘with’ ConstrApp} Note: To align `extends` clauses and `derives` clauses, Scala 3 also allows multiple extended types to be separated by commas. So the following is now legal: + ```scala class A extends B, C { ... } ``` + It is equivalent to the old form + ```scala class A extends B with C { ... } ``` From 70229a5c3c386ada6afecb8b607e38dedf97874c Mon Sep 17 00:00:00 2001 From: "Lan, Jian" Date: Sat, 26 Dec 2020 01:27:20 -0800 Subject: [PATCH 03/10] Fix a list by adding missing , to its elements in derivation.md --- docs/docs/reference/contextual/derivation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/derivation.md b/docs/docs/reference/contextual/derivation.md index 95137e9207be..fea8c3355e57 100644 --- a/docs/docs/reference/contextual/derivation.md +++ b/docs/docs/reference/contextual/derivation.md @@ -295,7 +295,7 @@ given derived$Eq[T](using eqT: Eq[T]): Eq[Opt[T]] = eqSum( summon[Mirror[Opt[T]]], List( - eqProduct(summon[Mirror[Sm[T]]], List(summon[Eq[T]])) + eqProduct(summon[Mirror[Sm[T]]], List(summon[Eq[T]])), eqProduct(summon[Mirror[Nn.type]], Nil) ) ) From 7299639e6205179b9a32a1b074a8dbc603e1797e Mon Sep 17 00:00:00 2001 From: "Lan, Jian" Date: Sat, 26 Dec 2020 01:54:23 -0800 Subject: [PATCH 04/10] Improve code examples in inline.md --- docs/docs/reference/metaprogramming/inline.md | 75 +++++++++++-------- 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/docs/docs/reference/metaprogramming/inline.md b/docs/docs/reference/metaprogramming/inline.md index cdbedaa9d48d..bb87d7792a18 100644 --- a/docs/docs/reference/metaprogramming/inline.md +++ b/docs/docs/reference/metaprogramming/inline.md @@ -144,22 +144,23 @@ Inline methods can override other non-inline methods. The rules are as follows: ```scala abstract class A: - def f(): Int - def g(): Int = f() + def f: Int + def g: Int = f class B extends A: - inline def f() = 22 - override inline def g() = f() + 11 + inline def f = 22 + override inline def g = f + 11 - val b = B() + val b = new B val a: A = b // inlined invocatons - assert(b.f() == 22) - assert(b.g() == 33) + assert(b.f == 22) + assert(b.g == 33) // dynamic invocations - assert(a.f() == 22) - assert(a.g() == 33) + assert(a.f == 22) + assert(a.g == 33) ``` + The inlined invocations and the dynamically dispatched invocations give the same results. 2. Inline methods are effectively final. @@ -168,14 +169,14 @@ Inline methods can override other non-inline methods. The rules are as follows: ```scala abstract class A: - inline def f(): Int + inline def f: Int object B extends A: - inline def f(): Int = 22 + inline def f: Int = 22 - B.f() // OK + B.f // OK val a: A = B - a.f() // error: cannot inline f() in A. + a.f // error: cannot inline f() in A. ``` ### Relationship to `@inline` @@ -224,6 +225,7 @@ including _platform-specific_ extensions such as constant folding of pure numeric computations. An inline value must have a literal type such as `1` or `true`. + ```scala inline val four = 4 // equivalent to @@ -249,17 +251,18 @@ specialized to a more precise type upon expansion. Example: ```scala class A class B extends A: - def m() = true + def m = true transparent inline def choose(b: Boolean): A = - if b then new A() else new B() + if b then new A else new B val obj1 = choose(true) // static type is A val obj2 = choose(false) // static type is B -// obj1.m() // compile-time error: `m` is not defined on `A` -obj2.m() // OK +// obj1.m // compile-time error: `m` is not defined on `A` +obj2.m // OK ``` + Here, the inline method `choose` returns an instance of either of the two types `A` or `B`. If `choose` had not been declared to be `transparent`, the result of its expansion would always be of type `A`, even though the computed value might be of the subtype `B`. @@ -276,9 +279,9 @@ the singleton type `0` permitting the addition to be ascribed with the correct type `1`. ```scala -transparent inline def zero(): Int = 0 +transparent inline def zero: Int = 0 -val one: 1 = zero() + 1 +val one: 1 = zero + 1 ``` ## Inline Conditionals @@ -295,6 +298,7 @@ inline def update(delta: Int) = inline if delta >= 0 then increaseBy(delta) else decreaseBy(-delta) ``` + A call `update(22)` would rewrite to `increaseBy(22)`. But if `update` was called with a value that was not a compile-time constant, we would get a compile time error like the one below: @@ -343,10 +347,10 @@ case class Succ[N <: Nat](n: N) extends Nat transparent inline def toInt(n: Nat): Int = inline n match - case Zero => 0 + case Zero => 0 case Succ(n1) => toInt(n1) + 1 -final val natTwo = toInt(Succ(Succ(Zero))) +inline val natTwo = toInt(Succ(Succ(Zero))) val intTwo: 2 = natTwo ``` @@ -366,10 +370,10 @@ import scala.compiletime.{constValue, S} transparent inline def toIntC[N]: Int = inline constValue[N] match - case 0 => 0 + case 0 => 0 case _: S[n1] => 1 + toIntC[n1] -final val ctwo = toIntC[2] +inline val ctwo = toIntC[2] ``` `constValueOpt` is the same as `constValue`, however returning an `Option[T]` @@ -402,19 +406,20 @@ import scala.compiletime.erasedValue inline def defaultValue[T] = inline erasedValue[T] match - case _: Byte => Some(0: Byte) - case _: Char => Some(0: Char) - case _: Short => Some(0: Short) - case _: Int => Some(0) - case _: Long => Some(0L) - case _: Float => Some(0.0f) - case _: Double => Some(0.0d) + case _: Byte => Some(0: Byte) + case _: Char => Some(0: Char) + case _: Short => Some(0: Short) + case _: Int => Some(0) + case _: Long => Some(0L) + case _: Float => Some(0.0f) + case _: Double => Some(0.0d) case _: Boolean => Some(false) - case _: Unit => Some(()) - case _ => None + case _: Unit => Some(()) + case _ => None ``` Then: + ```scala val dInt: Some[Int] = defaultValue[Int] val dDouble: Some[Double] = defaultValue[Double] @@ -434,7 +439,7 @@ transparent inline def toIntT[N <: Nat]: Int = case _: Zero.type => 0 case _: Succ[n] => toIntT[n] + 1 -final val two = toIntT[Succ[Succ[Zero.type]]] +inline val two = toIntT[Succ[Succ[Zero.type]]] ``` `erasedValue` is an `erased` method so it cannot be used and has no runtime @@ -555,6 +560,7 @@ keep the viral nature of implicit search programs based on logic programming. By contrast, the new `summonFrom` construct makes implicit search available in a functional context. To solve the problem of creating the right set, one would use it as follows: + ```scala import scala.compiletime.summonFrom @@ -563,12 +569,14 @@ inline def setFor[T]: Set[T] = summonFrom { case _ => new HashSet[T] } ``` + A `summonFrom` call takes a pattern matching closure as argument. All patterns in the closure are type ascriptions of the form `identifier : Type`. Patterns are tried in sequence. The first case with a pattern `x: T` such that an implicit value of type `T` can be summoned is chosen. Alternatively, one can also use a pattern-bound given instance, which avoids the explicit using clause. For instance, `setFor` could also be formulated as follows: + ```scala import scala.compiletime.summonFrom @@ -606,6 +614,7 @@ inline def f: Any = summonFrom { ## `summonInline` The shorthand `summonInline` provides a simple way to write a `summon` that is delayed until the call is inlined. + ```scala transparent inline def summonInline[T]: T = summonFrom { case t: T => t From 4a59cd2da3885caf7470799c506aa8a7cab3be02 Mon Sep 17 00:00:00 2001 From: "Lan, Jian" Date: Sat, 26 Dec 2020 01:55:49 -0800 Subject: [PATCH 05/10] Fix a code example in macros.md --- docs/docs/reference/metaprogramming/macros.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/metaprogramming/macros.md b/docs/docs/reference/metaprogramming/macros.md index 6fb53a245d1c..eb443338d9df 100644 --- a/docs/docs/reference/metaprogramming/macros.md +++ b/docs/docs/reference/metaprogramming/macros.md @@ -331,7 +331,7 @@ a `List` is liftable if its element type is: given [T: ToExpr : Type]: ToExpr[List[T]] with def toExpr(xs: List[T]) = xs match case head :: tail => '{ ${ Expr(head) } :: ${ toExpr(tail) } } - case Nil => '{ Nil: List[T] } + case Nil => '{ Nil: List[T] } ``` In the end, `ToExpr` resembles very much a serialization @@ -762,7 +762,7 @@ trait Show[-T]: def show(x: T): String // in a different file -given Show[Boolean]: +given Show[Boolean] with def show(b: Boolean) = "boolean!" println(showMe"${true}") From a8a45d0f49d173ce3e3c62a55af641ddb7b370c0 Mon Sep 17 00:00:00 2001 From: "Lan, Jian" Date: Sat, 26 Dec 2020 02:05:30 -0800 Subject: [PATCH 06/10] Adjust the format of creator-applications.md --- .../creator-applications.md | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/docs/docs/reference/other-new-features/creator-applications.md b/docs/docs/reference/other-new-features/creator-applications.md index 4ac09f49e84f..387202720f9c 100644 --- a/docs/docs/reference/other-new-features/creator-applications.md +++ b/docs/docs/reference/other-new-features/creator-applications.md @@ -3,9 +3,11 @@ layout: doc-page title: "Universal Apply Methods" --- -Scala case classes generate apply methods, so that values of case classes can be created using simple function application, without needing to write `new`. +Scala case classes generate apply methods, so that values of case classes can be created using simple +function application, without needing to write `new`. Scala 3 generalizes this scheme to all concrete classes. Example: + ```scala class StringBuilder(s: String): def this() = this("") @@ -13,28 +15,35 @@ class StringBuilder(s: String): StringBuilder("abc") // same as new StringBuilder("abc") StringBuilder() // same as new StringBuilder() ``` -This works since a companion object with two apply methods + +This works since a companion object with two `apply` methods is generated together with the class. The object looks like this: + ```scala object StringBuilder: inline def apply(s: String): StringBuilder = new StringBuilder(s) inline def apply(): StringBuilder = new StringBuilder() ``` + The synthetic object `StringBuilder` and its `apply` methods are called _constructor proxies_. Constructor proxies are generated even for Java classes and classes coming from Scala 2. The precise rules are as follows: - 1. A constructor proxy companion object `object C` is created for a concrete class `C`, provided the class does not have already a companion, and there is also no other value or method named `C` defined or inherited in the scope where `C` is defined. + 1. A constructor proxy companion object `object C` is created for a concrete class `C`, + provided the class does not have already a companion, and there is also no other value + or method named `C` defined or inherited in the scope where `C` is defined. 2. Constructor proxy `apply` methods are generated for a concrete class provided - - the class has a companion object (which might have been generated in step 1), and - - that companion object does not already define a member named `apply`. + - the class has a companion object (which might have been generated in step 1), and + - that companion object does not already define a member named `apply`. - Each generated `apply` method forwards to one constructor of the class. It has the - same type and value parameters as the constructor. + Each generated `apply` method forwards to one constructor of the class. It has the + same type and value parameters as the constructor. -Constructor proxy companions cannot be used as values by themselves. A proxy companion object must be selected with `apply` (or be applied to arguments, in which case the `apply` is implicitly inserted). +Constructor proxy companions cannot be used as values by themselves. A proxy companion object must +be selected with `apply` (or be applied to arguments, in which case the `apply` is implicitly +inserted). Constructor proxies are also not allowed to shadow normal definitions. That is, if an identifier resolves to a constructor proxy, and the same identifier is also @@ -42,4 +51,6 @@ defined or imported in some other scope, an ambiguity is reported. ### Motivation -Leaving out `new` hides an implementation detail and makes code more pleasant to read. Even though it requires a new rule, it will likely increase the perceived regularity of the language, since case classes already provide function call creation syntax (and are often defined for this reason alone). +Leaving out `new` hides an implementation detail and makes code more pleasant to read. Even though +it requires a new rule, it will likely increase the perceived regularity of the language, since case +classes already provide function call creation syntax (and are often defined for this reason alone). From 93df033c9a0b973b9e56473d5aa6b116f25dc47e Mon Sep 17 00:00:00 2001 From: "Lan, Jian" Date: Sat, 26 Dec 2020 02:09:48 -0800 Subject: [PATCH 07/10] Improve code examples in opaques.md --- .../reference/other-new-features/opaques.md | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/docs/reference/other-new-features/opaques.md b/docs/docs/reference/other-new-features/opaques.md index f7711f0a10f6..5bed38293b21 100644 --- a/docs/docs/reference/other-new-features/opaques.md +++ b/docs/docs/reference/other-new-features/opaques.md @@ -61,6 +61,7 @@ l / l2 // error: `/` is not a member of Logarithm ### Bounds For Opaque Type Aliases Opaque type aliases can also come with bounds. Example: + ```scala object Access: @@ -85,11 +86,12 @@ object Access: end Access ``` + The `Access` object defines three opaque type aliases: - - `Permission`, representing a single permission, - - `Permissions`, representing a set of permissions with the meaning "all of these permissions granted", - - `PermissionChoice`, representing a set of permissions with the meaning "at least one of these permissions granted". +- `Permission`, representing a single permission, +- `Permissions`, representing a set of permissions with the meaning "all of these permissions granted", +- `PermissionChoice`, representing a set of permissions with the meaning "at least one of these permissions granted". Outside the `Access` object, values of type `Permissions` may be combined using the `&` operator, where `x & y` means "all permissions in `x` *and* in `y` granted". @@ -106,6 +108,7 @@ All three opaque type aliases have the same underlying representation type `Int` `Permission` type has an upper bound `Permissions & PermissionChoice`. This makes it known outside the `Access` object that `Permission` is a subtype of the other two types. Hence, the following usage scenario type-checks. + ```scala object User: import Access._ @@ -116,16 +119,17 @@ object User: val rwItem = Item(ReadWrite) val noItem = Item(NoPermission) - assert( roItem.rights.is(ReadWrite) == false ) - assert( roItem.rights.isOneOf(ReadOrWrite) == true ) + assert(!roItem.rights.is(ReadWrite)) + assert(roItem.rights.isOneOf(ReadOrWrite)) - assert( rwItem.rights.is(ReadWrite) == true ) - assert( rwItem.rights.isOneOf(ReadOrWrite) == true ) + assert(rwItem.rights.is(ReadWrite)) + assert(rwItem.rights.isOneOf(ReadOrWrite)) - assert( noItem.rights.is(ReadWrite) == false ) - assert( noItem.rights.isOneOf(ReadOrWrite) == false ) + assert(!noItem.rights.is(ReadWrite)) + assert(!noItem.rights.isOneOf(ReadOrWrite)) end User ``` + On the other hand, the call `roItem.rights.isOneOf(ReadWrite)` would give a type error since `Permissions` and `PermissionChoice` are different, unrelated types outside `Access`. From 302e37acf321a9046575ee58660b3e7fcead9914 Mon Sep 17 00:00:00 2001 From: "Lan, Jian" Date: Sat, 26 Dec 2020 02:16:47 -0800 Subject: [PATCH 08/10] Fix and improve numeric-literals.md --- .../changed-features/numeric-literals.md | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/docs/docs/reference/changed-features/numeric-literals.md b/docs/docs/reference/changed-features/numeric-literals.md index 9d1ce6b1ab49..843c348fffa1 100644 --- a/docs/docs/reference/changed-features/numeric-literals.md +++ b/docs/docs/reference/changed-features/numeric-literals.md @@ -24,36 +24,41 @@ how large they can be. The meaning of a numeric literal is determined as follows: - - If the literal ends with `l` or `L`, it is a `Long` integer (and must fit - in its legal range). - - If the literal ends with `f` or `F`, it is a single precision floating point number of type `Float`. - - If the literal ends with `d` or `D`, it is a double precision floating point number of type `Double`. +- If the literal ends with `l` or `L`, it is a `Long` integer (and must fit in its legal range). +- If the literal ends with `f` or `F`, it is a single precision floating point number of type `Float`. +- If the literal ends with `d` or `D`, it is a double precision floating point number of type `Double`. In each of these cases the conversion to a number is exactly as in Scala 2 or in Java. If a numeric literal does _not_ end in one of these suffixes, its meaning is determined by the expected type: - 1. If the expected type is `Int`, `Long`, `Float`, or `Double`, the literal is +1. If the expected type is `Int`, `Long`, `Float`, or `Double`, the literal is treated as a standard literal of that type. - 2. If the expected type is a fully defined type `T` that has a given instance of type +2. If the expected type is a fully defined type `T` that has a given instance of type `scala.util.FromDigits[T]`, the literal is converted to a value of type `T` by passing it as an argument to the `fromDigits` method of that instance (more details below). - 3. Otherwise, the literal is treated as a `Double` literal (if it has a decimal point or an +3. Otherwise, the literal is treated as a `Double` literal (if it has a decimal point or an exponent), or as an `Int` literal (if not). (This last possibility is again as in Scala 2 or Java.) With these rules, the definition + ```scala val x: Long = -10_000_000_000 ``` + is legal by rule (1), since the expected type is `Long`. The definitions + ```scala val y: BigInt = 0x123_abc_789_def_345_678_901 val z: BigDecimal = 111222333444.55 ``` + are legal by rule (2), since both `BigInt` and `BigDecimal` have `FromDigits` instances (which implement the `FromDigits` subclasses `FromDigits.WithRadix` and `FromDigits.Decimal`, respectively). On the other hand, + ```scala val x = -10_000_000_000 ``` + gives a type error, since without an expected type `-10_000_000_000` is treated by rule (3) as an `Int` literal, but it is too large for that type. ### The FromDigits Trait @@ -61,10 +66,12 @@ gives a type error, since without an expected type `-10_000_000_000` is treated To allow numeric literals, a type simply has to define a `given` instance of the `scala.util.FromDigits` type class, or one of its subclasses. `FromDigits` is defined as follows: + ```scala trait FromDigits[T]: def fromDigits(digits: String): T ``` + Implementations of the `fromDigits` convert strings of digits to the values of the implementation type `T`. The `digits` string consists of digits between `0` and `9`, possibly preceded by a @@ -74,6 +81,7 @@ the string is passed to `fromDigits`. The companion object `FromDigits` also defines subclasses of `FromDigits` for whole numbers with a given radix, for numbers with a decimal point, and for numbers that can have both a decimal point and an exponent: + ```scala object FromDigits: @@ -95,6 +103,7 @@ object FromDigits: */ trait Floating[T] extends Decimal[T] ``` + A user-defined number type can implement one of those, which signals to the compiler that hexadecimal numbers, decimal points, or exponents are also accepted in literals for this type. @@ -104,6 +113,7 @@ for this type. `FromDigits` implementations can signal errors by throwing exceptions of some subtype of `FromDigitsException`. `FromDigitsException` is defined with three subclasses in the `FromDigits` object as follows: + ```scala abstract class FromDigitsException(msg: String) extends NumberFormatException(msg) @@ -115,17 +125,22 @@ class MalformedNumber(msg: String = "malformed number literal") extends FromDigi ### Example As a fully worked out example, here is an implementation of a new numeric class, `BigFloat`, that accepts numeric literals. `BigFloat` is defined in terms of a `BigInt` mantissa and an `Int` exponent: + ```scala case class BigFloat(mantissa: BigInt, exponent: Int): override def toString = s"${mantissa}e${exponent}" ``` + `BigFloat` literals can have a decimal point as well as an exponent. E.g. the following expression should produce the `BigFloat` number `BigFloat(-123, 997)`: + ```scala -0.123E+1000: BigFloat ``` + The companion object of `BigFloat` defines an `apply` constructor method to construct a `BigFloat` from a `digits` string. Here is a possible implementation: + ```scala object BigFloat: import scala.util.FromDigits @@ -149,13 +164,16 @@ object BigFloat: (intPart, givenExponent) BigFloat(BigInt(intPart), exponent) ``` + To accept `BigFloat` literals, all that's needed in addition is a `given` instance of type `FromDigits.Floating[BigFloat]`: + ```scala given FromDigits: FromDigits.Floating[BigFloat] with def fromDigits(digits: String) = apply(digits) end BigFloat ``` + Note that the `apply` method does not check the format of the `digits` argument. It is assumed that only valid arguments are passed. For calls coming from the compiler that assumption is valid, since the compiler will first check whether a numeric @@ -164,18 +182,23 @@ literal has the correct format before it gets passed on to a conversion method. ### Compile-Time Errors With the setup of the previous section, a literal like + ```scala 1e10_0000_000_000: BigFloat ``` + would be expanded by the compiler to + ```scala BigFloat.FromDigits.fromDigits("1e100000000000") ``` + Evaluating this expression throws a `NumberTooLarge` exception at run time. We would like it to produce a compile-time error instead. We can achieve this by tweaking the `BigFloat` class with a small dose of metaprogramming. The idea is to turn the `fromDigits` method into a macro, i.e. make it an inline method with a splice as right hand side. To do this, replace the `FromDigits` instance in the `BigFloat` object by the following two definitions: + ```scala object BigFloat: ... @@ -183,16 +206,18 @@ object BigFloat: class FromDigits extends FromDigits.Floating[BigFloat]: def fromDigits(digits: String) = apply(digits) - given FromDigits: + given FromDigits with override inline def fromDigits(digits: String) = ${ fromDigitsImpl('digits) } ``` + Note that an inline method cannot directly fill in for an abstract method, since it produces no code that can be executed at runtime. That is why we define an intermediary class `FromDigits` that contains a fallback implementation which is then overridden by the inline method in the `FromDigits` given instance. That method is defined in terms of a macro implementation method `fromDigitsImpl`. Here is its definition: + ```scala private def fromDigitsImpl(digits: Expr[String])(using ctx: Quotes): Expr[BigFloat] = digits.value match @@ -207,6 +232,7 @@ implementation method `fromDigitsImpl`. Here is its definition: '{apply($digits)} end BigFloat ``` + The macro implementation takes an argument of type `Expr[String]` and yields a result of type `Expr[BigFloat]`. It tests whether its argument is a constant string. If that is the case, it converts the string using the `apply` method @@ -218,10 +244,13 @@ The interesting part is the `catch` part of the case where `digits` is constant. If the `apply` method throws a `FromDigitsException`, the exception's message is issued as a compile time error in the `ctx.error(ex.getMessage)` call. With this new implementation, a definition like + ```scala val x: BigFloat = 1234.45e3333333333 ``` + would give a compile time error message: + ```scala 3 | val x: BigFloat = 1234.45e3333333333 | ^^^^^^^^^^^^^^^^^^ From cee157e0a68de23a4164d88a088d710a74a84c9b Mon Sep 17 00:00:00 2001 From: "Lan, Jian" Date: Sat, 26 Dec 2020 21:42:33 -0800 Subject: [PATCH 09/10] Rephrase limit22.md --- docs/docs/reference/dropped-features/limit22.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/docs/reference/dropped-features/limit22.md b/docs/docs/reference/dropped-features/limit22.md index 05142d56661a..b204e2f30d3c 100644 --- a/docs/docs/reference/dropped-features/limit22.md +++ b/docs/docs/reference/dropped-features/limit22.md @@ -3,12 +3,14 @@ layout: doc-page title: "Dropped: Limit 22" --- -The limits of 22 for the maximal number of parameters of function types -and the maximal number of fields in tuple types have been dropped. +The limits of 22 for the maximal number of parameters of function types and the +maximal number of fields in tuple types have been dropped. -Functions can now have an arbitrary number of -parameters. Functions beyond `Function22` are erased to a new trait -`scala.FunctionXXL` and tuples beyond `Tuple22` are erased to a new trait `scala.TupleXXL`. -Both of these are implemented using arrays. +* Functions can now have an arbitrary number of parameters. Functions beyond + `Function22` are erased to a new trait `scala.FunctionXXL` + +* Tuples can also have an arbitrary number of fields. Tuples beyond `Tuple22` + are erased to a new trait `scala.TupleXXL`. Furthermore, they support generic + operation such as concatenation and indexing. -Tuples can also have an arbitrary number of fields. Furthermore, they support generic operation such as concatenation and indexing. +Both of these are implemented using arrays. From 215e04a4505253462bf45a39cfa57967afae61b9 Mon Sep 17 00:00:00 2001 From: "Lan, Jian" Date: Sat, 26 Dec 2020 21:44:15 -0800 Subject: [PATCH 10/10] Fix the view of match-syntax.md by adjusting the indentation of an ordered list --- .../changed-features/match-syntax.md | 64 ++++++++++--------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/docs/docs/reference/changed-features/match-syntax.md b/docs/docs/reference/changed-features/match-syntax.md index edada868b275..ed4ec87e2380 100644 --- a/docs/docs/reference/changed-features/match-syntax.md +++ b/docs/docs/reference/changed-features/match-syntax.md @@ -6,45 +6,47 @@ title: Match Expressions The syntactical precedence of match expressions has been changed. `match` is still a keyword, but it is used like an alphabetical operator. This has several consequences: - 1. `match` expressions can be chained: +1. `match` expressions can be chained: + + ```scala + xs match { + case Nil => "empty" + case x :: xs1 => "nonempty" + } match { + case "empty" => 0 + case "nonempty" => 1 + } + ``` + + (or, dropping the optional braces) + + ```scala + xs match + case Nil => "empty" + case x :: xs1 => "nonempty" + match + case "empty" => 0 + case "nonempty" => 1 + ``` + +2. `match` may follow a period: ```scala - xs match { - case Nil => "empty" - case x :: xs1 => "nonempty" - } match { - case "empty" => 0 - case "nonempty" => 1 - } + if xs.match + case Nil => false + case _ => true + then "nonempty" + else "empty" ``` - (or, dropping the optional braces) - - ```scala - xs match - case Nil => "empty" - case x :: xs1 => "nonempty" - match - case "empty" => 0 - case "nonempty" => 1 - ``` - - 2. `match` may follow a period: - - ```scala - if xs.match - case Nil => false - case _ => true - then "nonempty" - else "empty" - ``` - - 3. The scrutinee of a match expression must be an `InfixExpr`. Previously the scrutinee could be followed by a type ascription `: T`, but this is no longer supported. So `x : T match { ... }` now has to be - written `(x: T) match { ... }`. +3. The scrutinee of a match expression must be an `InfixExpr`. Previously the scrutinee could be + followed by a type ascription `: T`, but this is no longer supported. So `x : T match { ... }` + now has to be written `(x: T) match { ... }`. ## Syntax The new syntax of match expressions is as follows. + ``` InfixExpr ::= ... | InfixExpr MatchClause