Skip to content

Improve docs #10923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Dec 27, 2020
64 changes: 33 additions & 31 deletions docs/docs/reference/changed-features/match-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 37 additions & 8 deletions docs/docs/reference/changed-features/numeric-literals.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,47 +24,54 @@ 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

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
Expand All @@ -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:

Expand All @@ -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.
Expand All @@ -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)

Expand All @@ -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
Expand All @@ -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
Expand All @@ -164,35 +182,42 @@ 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:
...

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
Expand All @@ -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
Expand All @@ -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
| ^^^^^^^^^^^^^^^^^^
Expand Down
9 changes: 5 additions & 4 deletions docs/docs/reference/contextual/derivation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -297,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)
)
)
Expand Down Expand Up @@ -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 { ... }
```
Expand Down
Loading