|
| 1 | +--- |
| 2 | +layout: doc-page |
| 3 | +title: "Named Tuples" |
| 4 | +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/named-tuples.html |
| 5 | +--- |
| 6 | + |
| 7 | +The elements of a tuple can now be named. Example: |
| 8 | +```scala |
| 9 | +type Person = (name: String, age: Int) |
| 10 | +val Bob: Person = (name = "Bob", age = 33) |
| 11 | + |
| 12 | +Bob match |
| 13 | + case (name, age) => |
| 14 | + println(s"$name is $age years old") |
| 15 | + |
| 16 | +val persons: List[Person] = ... |
| 17 | +val minors = persons.filter: p => |
| 18 | + p.age < 18 |
| 19 | +``` |
| 20 | +Named bindings in tuples are similar to function parameters and arguments. We use `name: Type` for element types and `name = value` for element values. It is illegal to mix named and unnamed elements in a tuple, or to use the same same |
| 21 | +name for two different elements. |
| 22 | + |
| 23 | +Fields of named tuples can be selected by their name, as in the line `p.age < 18` above. |
| 24 | + |
| 25 | +### Conformance |
| 26 | + |
| 27 | +The order of names in a named tuple matters. For instance, the type `Person` above and the type `(age: Int, name: String)` would be different, incompatible types. |
| 28 | + |
| 29 | +Values of named tuple types can also be be defined using regular tuples. For instance: |
| 30 | +```scala |
| 31 | +val x: Person = ("Laura", 25) |
| 32 | + |
| 33 | +def register(person: Person) = ... |
| 34 | +register(person = ("Silvain", 16)) |
| 35 | +register(("Silvain", 16)) |
| 36 | +``` |
| 37 | +This follows since a regular tuple `(T_1, ..., T_n)` is treated as a subtype of a named tuple `(N_1 = T_1, ..., N_n = T_n)` with the same element types. On the other hand, named tuples do not conform to unnamed tuples, so the following is an error: |
| 38 | +```scala |
| 39 | +val x: (String, Int) = Bob // error: type mismatch |
| 40 | +``` |
| 41 | +One can convert a named tuple to an unnamed tuple with the `dropNames` method, so the following works: |
| 42 | +```scala |
| 43 | +val x: (String, Int) = Bob.dropNames // ok |
| 44 | +``` |
| 45 | +Note that conformance rules for named tuples are analogous to the rules for named parameters. One can assign parameters by position to a named parameter list. |
| 46 | +```scala |
| 47 | + def f(param: Int) = ... |
| 48 | + f(param = 1) // OK |
| 49 | + f(2) // Also OK |
| 50 | +``` |
| 51 | +But one cannot use a name to pass an argument to an unnamed parameter: |
| 52 | +```scala |
| 53 | + val f: Int => T |
| 54 | + f(2) // OK |
| 55 | + f(param = 2) // Not OK |
| 56 | +``` |
| 57 | +The rules for tuples are analogous. Unnamed tuples conform to named tuple types, but the opposite does not hold. |
| 58 | + |
| 59 | + |
| 60 | +### Pattern Matching |
| 61 | + |
| 62 | +When pattern matching on a named tuple, the pattern may be named or unnamed. |
| 63 | +If the pattern is named it needs to mention only a subset of the tuple names, and these names can come in any order. So the following are all OK: |
| 64 | +```scala |
| 65 | +Bob match |
| 66 | + case (name, age) => ... |
| 67 | + |
| 68 | +Bob match |
| 69 | + case (name = x, age = y) => ... |
| 70 | + |
| 71 | +Bob match |
| 72 | + case (age = x) => ... |
| 73 | + |
| 74 | +Bob match |
| 75 | + case (age = x, name = y) => ... |
| 76 | +``` |
| 77 | + |
| 78 | +### Expansion |
| 79 | + |
| 80 | +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` |
| 81 | +in package `scala` as an opaque type as follows: |
| 82 | +```scala |
| 83 | + opaque type NamedTuple[N <: Tuple, +V <: Tuple] >: V = V |
| 84 | +``` |
| 85 | +For instance, the `Person` type would be represented as the type |
| 86 | +```scala |
| 87 | +NamedTuple[("name", "age"), (String, Int)] |
| 88 | +``` |
| 89 | +`NamedTuple` is an opaque type alias of its second, value parameter. The first parameter is a string constant type which determines the name of the element. Since the type is just an alias of its value part, names are erased at runtime, and named tuples and regular tuples have the same representation. |
| 90 | + |
| 91 | +A `NamedTuple[N, V]` type is publicly known to be a supertype (but not a subtype) of its value paramater `V`, which means that regular tuples can be assigned to named tuples but not _vice versa_. |
| 92 | + |
| 93 | +The `NamedTuple` object contains a number of extension methods for named tuples hat mirror the same functions in `Tuple`. Examples are |
| 94 | +`apply`, `head`, `tail`, `take`, `drop`, `++`, `map`, or `zip`. |
| 95 | +Similar to `Tuple`, the `NamedTuple` object also contains types such as `Elem`, `Head`, `Concat` |
| 96 | +that describe the results of these extension methods. |
| 97 | + |
| 98 | +The translation of named tuples to instances of `NamedTuple` is fixed by the specification and therefore known to the programmer. This means that: |
| 99 | + |
| 100 | + - All tuple operations also work with named tuples "out of the box". |
| 101 | + - Macro libraries can rely on this expansion. |
| 102 | + |
| 103 | +### Restrictions |
| 104 | + |
| 105 | +The following restrictions apply to named tuple elements: |
| 106 | + |
| 107 | + 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: |
| 108 | + ```scala |
| 109 | + val illFormed1 = ("Bob", age = 33) // error |
| 110 | + ``` |
| 111 | + 2. Each element name in a named tuple must be unique. For instance, the following is in error: |
| 112 | + ```scala |
| 113 | + val illFormed2 = (name = "", age = 0, name = true) // error |
| 114 | + ``` |
| 115 | + 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: |
| 116 | + ```scala |
| 117 | + (tuple: Tuple) match |
| 118 | + case (age = x) => // error |
| 119 | + ``` |
| 120 | + |
| 121 | +### Syntax |
| 122 | + |
| 123 | +The syntax of Scala is extended as follows to support named tuples: |
| 124 | +``` |
| 125 | +SimpleType ::= ... |
| 126 | + | ‘(’ NameAndType {‘,’ NameAndType} ‘)’ |
| 127 | +NameAndType ::= id ':' Type |
| 128 | + |
| 129 | +SimpleExpr ::= ... |
| 130 | + | '(' NamedExprInParens {‘,’ NamedExprInParens} ')' |
| 131 | +NamedExprInParens ::= id '=' ExprInParens |
| 132 | + |
| 133 | +SimplePattern ::= ... |
| 134 | + | '(' NamedPattern {‘,’ NamedPattern} ')' |
| 135 | +NamedPattern ::= id '=' Pattern |
| 136 | +``` |
0 commit comments