You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: content/named-tuples.md
+46-13Lines changed: 46 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -65,29 +65,46 @@ Example:
65
65
println(x)
66
66
~~~
67
67
68
-
### Conformance
68
+
### Conformance and Convertibility
69
69
70
70
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.
71
71
72
72
Values of named tuple types can also be be defined using regular tuples. For instance:
73
73
```scala
74
-
valx:Person= ("Laura", 25)
74
+
valLaura:Person= ("Laura", 25)
75
75
76
76
defregister(person: Person) = ...
77
77
register(person = ("Silvain", 16))
78
78
register(("Silvain", 16))
79
79
```
80
-
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:
81
-
```scala
82
-
valx: (String, Int) =Bob// error: type mismatch
83
-
```
84
-
One can convert a named tuple to an unnamed tuple with the `toTuple` method, so the following works:
80
+
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.
81
+
82
+
In the other direction, one can convert a named tuple to an unnamed tuple with the `toTuple` method. Example:
85
83
```scala
86
84
valx: (String, Int) =Bob.toTuple // ok
87
85
```
86
+
`toTuple` is defined as an extension method in the `NamedTuple` object.
87
+
It returns the given tuple unchanged and simply "forgets" the names.
88
88
89
-
_Question:_ Should we define an implicit conversion, either in place of this method or in addition to it?
90
-
89
+
A `.toTuple` selection is inserted implicitly by the compiler if it encounters a named tuple but the expected type is a regular tuple. So the following works as well:
90
+
```scala
91
+
valx: (String, Int) =Bob// works, expanded to Bob.toTuple
92
+
```
93
+
The difference between subtyping in one direction and automatic `.toTuple` conversions in the other is relatively minor. The main difference is that `.toTuple` conversions don't work inside type constructors. So the following is OK:
94
+
```scala
95
+
valnames=List("Laura", "Silvain")
96
+
valages=List(25, 16)
97
+
valpersons:List[Person] = names.zip(ages)
98
+
```
99
+
But the following would be illegal.
100
+
```scala
101
+
valpersons:List[Person] =List(Bob, Laura)
102
+
valpairs:List[(String, Int)] = persons // error
103
+
```
104
+
We would need an explicit `_.toTuple` selection to express this:
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.
92
109
```scala
93
110
deff(param: Int) = ...
@@ -100,8 +117,7 @@ But one cannot use a name to pass an argument to an unnamed parameter:
100
117
f(2) // OK
101
118
f(param =2) // Not OK
102
119
```
103
-
The rules for tuples are analogous. Unnamed tuples conform to named tuple types, but the opposite does not hold.
104
-
120
+
The rules for tuples are analogous. Unnamed tuples conform to named tuple types, but the opposite requires a conversion.
105
121
106
122
### Pattern Matching
107
123
@@ -327,12 +343,29 @@ By contrast to named tuples, structural types are unordered and have width subty
327
343
328
344
### Conformance
329
345
330
-
A large part of Pre-SIP discussion centered around subtyping rules,. whether ordinary tuples should subtype named-tuples (as in this proposal) or _vice versa_ or maybe no subtyping at all.
346
+
A large part of Pre-SIP discussion centered around subtyping rules, whether ordinary tuples should subtype named-tuples (as in this proposal) or _vice versa_ or maybe no subtyping at all.
331
347
332
-
Looking at precedent in other languages it feels like we we do want some sort of subtyping for easy convertibility and possibly an implicit conversion in the other direction.
348
+
Looking at precedent in other languages it feels like we we do want some sort of subtyping for easy convertibility and an implicit conversion in the other direction. This proposal picks _unnamed_ <: _named_ for the subtyping and _named_ -> _unnamed_ for the conversion.
333
349
334
350
The discussion established that both forms of subtyping are sound. My personal opinion is that the subtyping of this proposal is both more useful and safer than the one in the other direction. There is also the problem that changing the subtyping direction would be incompatible with the current structure of `Tuple` and `NamedTuple` since for instance `zip` is already an inline method on `Tuple` so it could not be overridden in `NamedTuple`. To make this work requires a refactoring of `Tuple` to use more extension methods, and the questions whether this is feasible and whether it can be made binary backwards compatible are unknown. I personally will not work on this, if others are willing to make the effort we can discuss the alternative subtyping as well.
335
351
352
+
_Addendum:_ Turning things around, adopting _named_ <: _unnamed_ for the subtyping and `_unnamed_ -> _named_ for the conversion leads to weaker typing with undetected errors. Consider:
353
+
```scala
354
+
typePerson= (name: String, age: Int)
355
+
valbob:Person
356
+
bob.zip((firstName: String, agee: Int))
357
+
```
358
+
This should report a type error.
359
+
But in the alternative scheme, we'd have `(firstName: String, agee: Int) <: (String, Int)` by subtyping and then
360
+
`(String, Int) -> (name: String, age: Int)` by implicit naming conversion. This is clearly not what we want.
361
+
362
+
By contrast, in the implemented scheme, we will not convert `(firstName: String, agee: Int)` to `(String, Int)` since a conversion is only attempted if the expected type is a regular tuple, and in our scenario it is a named tuple instead.
363
+
364
+
My takeaway is that these designs have rather subtle consequences and any alterations would need a full implementation before they can be judged. For instance, the situation with `zip` was a surprise to me, which came up since I first implemented `_.toTuple` as a regular implicit conversion instead of a compiler adaptation.
365
+
366
+
A possibly simpler design would be to drop all conformance and conversion rules. The problem with this approach is worse usability and problems with smooth migration. Migration will be an issue since right now everything is a regular tuple. If we make it hard to go from there to named tuples, everything will tend to stay a regular tuple and named tuples will be much less used than we would hope for.
367
+
368
+
336
369
### Spread Operator
337
370
338
371
An idea I was thinking of but that I did not include in this proposal highlights another potential problem with subtyping. Consider adding a _spread_ operator `*` for tuples and named tuples. if `x` is a tuple then `f(x*)` is `f` applied to all fields of `x` expanded as individual arguments. Likewise, if `y` is a named tuple, then `f(y*)` is `f` applied to all elements of `y` as named arguments.
0 commit comments