Skip to content

Commit

Permalink
Update reference docs
Browse files Browse the repository at this point in the history
  • Loading branch information
odersky committed Oct 1, 2024
1 parent 87cdbc8 commit 6263944
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
layout: doc-page
title: "Named Tuples"
nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/named-tuples.html
nightlyOf: https://docs.scala-lang.org/scala3/reference/other-new-features/named-tuples.html
---

The elements of a tuple can now be named. Example:
Starting in Scala 3.6, the elements of a tuple can be named. Example:
```scala
type Person = (name: String, age: Int)
val Bob: Person = (name = "Bob", age = 33)
Expand Down Expand Up @@ -94,6 +94,24 @@ Bob match
case (age = x, name = y) => ...
```

### Pattern Matching with Named Fields in General

We allow named patterns not just for named tuples but also for case classes. For instance:
```scala
city match
case c @ City(name = "London") => println(p.population)
case City(name = n, zip = 1026, population = pop) => println(pop)
```

Named constructor patterns are analogous to named tuple patterns. In both cases

- every name must match the name some field of the selector,
- names can come in any order,
- not all fields of the selector need to be matched.

Named patterns are compatible with extensible pattern matching simply because
`unapply` results can be named tuples.

### Expansion

Named tuples are in essence just a convenient syntax for regular tuples. In the internal representation, a named tuple type is represented at compile time as a pair of two tuples. One tuple contains the names as literal constant string types, the other contains the element types. The runtime representation of a named tuples consists of just the element values, whereas the names are forgotten. This is achieved by declaring `NamedTuple`
Expand All @@ -119,6 +137,47 @@ The translation of named tuples to instances of `NamedTuple` is fixed by the spe
- All tuple operations also work with named tuples "out of the box".
- Macro libraries can rely on this expansion.

### Computed Field Names

The `Selectable` trait now has a `Fields` type member that can be instantiated
to a named tuple.

```scala
trait Selectable:
type Fields <: NamedTuple.AnyNamedTuple
```

If `Fields` is instantiated in a subclass of `Selectable` to some named tuple type,
then the available fields and their types will be defined by that type. Assume `n: T`
is an element of the `Fields` type in some class `C` that implements `Selectable`,
that `c: C`, and that `n` is not otherwise legal as a name of a selection on `c`.
Then `c.n` is a legal selection, which expands to `c.selectDynamic("n").asInstanceOf[T]`.

It is the task of the implementation of `selectDynamic` in `C` to ensure that its
computed result conforms to the predicted type `T`

As an example, assume we have a query type `Q[T]` defined as follows:

```scala
trait Q[T] extends Selectable:
type Fields = NamedTuple.Map[NamedTuple.From[T], Q]
def selectDynamic(fieldName: String) = ...
```

Assume in the user domain:
```scala
case class City(zipCode: Int, name: String, population: Int)
val city: Q[City]
```
Then
```scala
city.zipCode
```
has type `Q[Int]` and it expands to
```scala
city.selectDynamic("zipCode").asInstanceOf[Q[Int]]
```

### The NamedTuple.From Type

The `NamedTuple` object contains a type definition
Expand All @@ -137,33 +196,36 @@ then `NamedTuple.From[City]` is the named tuple
(zip: Int, name: String, population: Int)
```
The same works for enum cases expanding to case classes, abstract types with case classes as upper bound, alias types expanding to case classes
and singleton types with case classes as underlying type.
and singleton types with case classes as underlying type (in terms of the implementation, the `classSymbol` of a type must be a case class).

`From` is also defined on named tuples. If `NT` is a named tuple type, then `From[NT] = NT`.


### Operations on Named Tuples

The operations on named tuples are defined in object [scala.NamedTuple](https://www.scala-lang.org/api/3.x/scala/NamedTuple$.html).

### Restrictions

The following restrictions apply to named tuple elements:
The following restrictions apply to named tuples and named pattern arguments:

1. Either all elements of a tuple are named or none are named. It is illegal to mix named and unnamed elements in a tuple. For instance, the following is in error:
1. Either all elements of a tuple or constructor pattern are named or none are named. It is illegal to mix named and unnamed elements in a tuple. For instance, the following is in error:
```scala
val illFormed1 = ("Bob", age = 33) // error
```
2. Each element name in a named tuple must be unique. For instance, the following is in error:
2. Each element name in a named tuple or constructor pattern must be unique. For instance, the following is in error:
```scala
val illFormed2 = (name = "", age = 0, name = true) // error
```
3. Named tuples can be matched with either named or regular patterns. But regular tuples and other selector types can only be matched with regular tuple patterns. For instance, the following is in error:
3. Named tuples and case classes can be matched with either named or regular patterns. But regular tuples and other selector types can only be matched with regular tuple patterns. For instance, the following is in error:
```scala
(tuple: Tuple) match
case (age = x) => // error
```
4. Regular selector names `_1`, `_2`, ... are not allowed as names in named tuples.
## Syntax Changes

### Syntax

The syntax of Scala is extended as follows to support named tuples:
The syntax of Scala is extended as follows to support named tuples and
named constructor arguments:
```
SimpleType ::= ...
| ‘(’ NameAndType {‘,’ NameAndType} ‘)’
Expand All @@ -178,31 +240,11 @@ Patterns ::= Pattern {‘,’ Pattern}
NamedPattern ::= id '=' Pattern
```
### Named Pattern Matching
We allow named patterns not just for named tuples but also for case classes.
For instance:
```scala
city match
case c @ City(name = "London") => println(p.population)
case City(name = n, zip = 1026, population = pop) => println(pop)
```

Named constructor patterns are analogous to named tuple patterns. In both cases

- either all fields are named or none is,
- every name must match the name some field of the selector,
- names can come in any order,
- not all fields of the selector need to be matched.

This revives SIP 43, with a much simpler desugaring than originally proposed.
Named patterns are compatible with extensible pattern matching simply because
`unapply` results can be named tuples.

### Source Incompatibilities
There are some source incompatibilities involving named tuples of length one.
First, what was previously classified as an assignment could now be interpreted as a named tuple. Example:
```scala
var age: Int
(age = 1)
Expand All @@ -221,43 +263,3 @@ c f (age = 1)
```
will now construct a tuple as second operand instead of passing a named parameter.

### Computed Field Names

The `Selectable` trait now has a `Fields` type member that can be instantiated
to a named tuple.

```scala
trait Selectable:
type Fields <: NamedTuple.AnyNamedTuple
```

If `Fields` is instantiated in a subclass of `Selectable` to some named tuple type,
then the available fields and their types will be defined by that type. Assume `n: T`
is an element of the `Fields` type in some class `C` that implements `Selectable`,
that `c: C`, and that `n` is not otherwise legal as a name of a selection on `c`.
Then `c.n` is a legal selection, which expands to `c.selectDynamic("n").asInstanceOf[T]`.

It is the task of the implementation of `selectDynamic` in `C` to ensure that its
computed result conforms to the predicted type `T`

As an example, assume we have a query type `Q[T]` defined as follows:

```scala
trait Q[T] extends Selectable:
type Fields = NamedTuple.Map[NamedTuple.From[T], Q]
def selectDynamic(fieldName: String) = ...
```

Assume in the user domain:
```scala
case class City(zipCode: Int, name: String, population: Int)
val city: Q[City]
```
Then
```scala
city.zipCode
```
has type `Q[Int]` and it expands to
```scala
city.selectDynamic("zipCode").asInstanceOf[Q[Int]]
```
2 changes: 1 addition & 1 deletion docs/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ subsection:
- page: reference/other-new-features/export.md
- page: reference/other-new-features/opaques.md
- page: reference/other-new-features/opaques-details.md
- page: reference/other-new-features/named-tuples.md
- page: reference/other-new-features/open-classes.md
- page: reference/other-new-features/parameter-untupling.md
- page: reference/other-new-features/parameter-untupling-spec.md
Expand Down Expand Up @@ -154,7 +155,6 @@ subsection:
- page: reference/experimental/cc.md
- page: reference/experimental/purefuns.md
- page: reference/experimental/tupled-function.md
- page: reference/experimental/named-tuples.md
- page: reference/experimental/modularity.md
- page: reference/experimental/typeclasses.md
- page: reference/experimental/runtimeChecked.md
Expand Down

0 comments on commit 6263944

Please sign in to comment.