@@ -146,6 +146,47 @@ The translation of named tuples to instances of `NamedTuple` is fixed by the spe
146
146
- All tuple operations also work with named tuples "out of the box".
147
147
- Macro libraries can rely on this expansion.
148
148
149
+ ### Computed Field Names
150
+
151
+ The ` Selectable ` trait now has a ` Fields ` type member that can be instantiated
152
+ to a named tuple.
153
+
154
+ ``` scala
155
+ trait Selectable :
156
+ type Fields <: NamedTuple .AnyNamedTuple
157
+ ```
158
+
159
+ If ` Fields ` is instantiated in a subclass of ` Selectable ` to some named tuple type,
160
+ then the available fields and their types will be defined by that type. Assume ` n: T `
161
+ is an element of the ` Fields ` type in some class ` C ` that implements ` Selectable ` ,
162
+ that ` c: C ` , and that ` n ` is not otherwise legal as a name of a selection on ` c ` .
163
+ Then ` c.n ` is a legal selection, which expands to ` c.selectDynamic("n").asInstanceOf[T] ` .
164
+
165
+ It is the task of the implementation of ` selectDynamic ` in ` C ` to ensure that its
166
+ computed result conforms to the predicted type ` T `
167
+
168
+ As an example, assume we have a query type ` Q[T] ` defined as follows:
169
+
170
+ ``` scala
171
+ trait Q [T ] extends Selectable :
172
+ type Fields = NamedTuple .Map [NamedTuple .From [T ], Q ]
173
+ def selectDynamic (fieldName : String ) = ...
174
+ ```
175
+
176
+ Assume in the user domain:
177
+ ``` scala
178
+ case class City (zipCode : Int , name : String , population : Int )
179
+ val city : Q [City ]
180
+ ```
181
+ Then
182
+ ``` scala
183
+ city.zipCode
184
+ ```
185
+ has type ` Q[Int] ` and it expands to
186
+ ``` scala
187
+ city.selectDynamic(" zipCode" ).asInstanceOf [Q [Int ]]
188
+ ```
189
+
149
190
### The NamedTuple.From Type
150
191
151
192
The ` NamedTuple ` object contains a type definition
@@ -154,15 +195,19 @@ The `NamedTuple` object contains a type definition
154
195
```
155
196
` From ` is treated specially by the compiler. When ` NamedTuple.From ` is applied to
156
197
an argument type that is an instance of a case class, the type expands to the named
157
- tuple consisting of all the fields of that case class. Here, fields means: elements of the first parameter section. For instance, assuming
198
+ tuple consisting of all the fields of that case class.
199
+ Here, _ fields_ means: elements of the first parameter section. For instance, assuming
158
200
``` scala
159
201
case class City (zip : Int , name : String , population : Int )
160
202
```
161
203
then ` NamedTuple.From[City] ` is the named tuple
162
204
``` scala
163
205
(zip : Int , name : String , population : Int )
164
206
```
165
- The same works for enum cases expanding to case classes.
207
+ The same works for enum cases expanding to case classes, abstract types with case classes as upper bound, alias types expanding to case classes
208
+ and singleton type with case classes as underlying type (in terms of the implementation, the ` classSymbol ` of a type must be a case class.
209
+
210
+ ` From ` is also defined on named tuples. If ` NT ` is a named tuple type, then ` From[NT] = NT ` .
166
211
167
212
### Pattern Matching with Named Fields in General
168
213
@@ -183,6 +228,9 @@ This revives SIP 43, with a much simpler desugaring than originally proposed.
183
228
Named patterns are compatible with extensible pattern matching simply because
184
229
` unapply ` results can be named tuples.
185
230
231
+ ### Operations on Named Tuples
232
+
233
+ The operations on named tuples are defined in object ` scala.NamedTuple ` . The current version of this object is listed in the appendix.
186
234
187
235
### Restrictions
188
236
@@ -298,3 +346,202 @@ This section should list prior work related to the proposal, notably:
298
346
299
347
## FAQ
300
348
349
+ ## Appendix: NamedTuple Definition
350
+
351
+ Here is the current definition of ` NamedTuple ` . This is part of the library and therefore subject to future changes and additions.
352
+
353
+ ``` scala
354
+ package scala
355
+ import annotation .experimental
356
+ import compiletime .ops .boolean .*
357
+
358
+ @ experimental
359
+ object NamedTuple :
360
+
361
+ opaque type AnyNamedTuple = Any
362
+ opaque type NamedTuple [N <: Tuple , + V <: Tuple ] >: V <: AnyNamedTuple = V
363
+
364
+ def apply [N <: Tuple , V <: Tuple ](x : V ): NamedTuple [N , V ] = x
365
+
366
+ def unapply [N <: Tuple , V <: Tuple ](x : NamedTuple [N , V ]): Some [V ] = Some (x)
367
+
368
+ extension [V <: Tuple ](x : V )
369
+ inline def withNames [N <: Tuple ]: NamedTuple [N , V ] = x
370
+
371
+ export NamedTupleDecomposition .{Names , DropNames }
372
+
373
+ extension [N <: Tuple , V <: Tuple ](x : NamedTuple [N , V ])
374
+
375
+ /** The underlying tuple without the names */
376
+ inline def toTuple : V = x
377
+
378
+ /** The number of elements in this tuple */
379
+ inline def size : Tuple .Size [V ] = toTuple.size
380
+
381
+ // This intentionally works for empty named tuples as well. I think NnEmptyTuple is a dead end
382
+ // and should be reverted, justy like NonEmptyList is also appealing at first, but a bad idea
383
+ // in the end.
384
+
385
+ /** The value (without the name) at index `n` of this tuple */
386
+ inline def apply (n : Int ): Tuple .Elem [V , n.type ] =
387
+ inline toTuple match
388
+ case tup : NonEmptyTuple => tup(n).asInstanceOf [Tuple .Elem [V , n.type ]]
389
+ case tup => tup.productElement(n).asInstanceOf [Tuple .Elem [V , n.type ]]
390
+
391
+ /** The first element value of this tuple */
392
+ inline def head : Tuple .Elem [V , 0 ] = apply(0 )
393
+
394
+ /** The tuple consisting of all elements of this tuple except the first one */
395
+ inline def tail : Tuple .Drop [V , 1 ] = toTuple.drop(1 )
396
+
397
+ /** The last element value of this tuple */
398
+ inline def last : Tuple .Last [V ] = apply(size - 1 ).asInstanceOf [Tuple .Last [V ]]
399
+
400
+ /** The tuple consisting of all elements of this tuple except the last one */
401
+ inline def init : Tuple .Init [V ] = toTuple.take(size - 1 ).asInstanceOf [Tuple .Init [V ]]
402
+
403
+ /** The tuple consisting of the first `n` elements of this tuple, or all
404
+ * elements if `n` exceeds `size`.
405
+ */
406
+ inline def take (n : Int ): NamedTuple [Tuple .Take [N , n.type ], Tuple .Take [V , n.type ]] =
407
+ toTuple.take(n)
408
+
409
+ /** The tuple consisting of all elements of this tuple except the first `n` ones,
410
+ * or no elements if `n` exceeds `size`.
411
+ */
412
+ inline def drop (n : Int ): NamedTuple [Tuple .Drop [N , n.type ], Tuple .Drop [V , n.type ]] =
413
+ toTuple.drop(n)
414
+
415
+ /** The tuple `(x.take(n), x.drop(n))` */
416
+ inline def splitAt (n : Int ): NamedTuple [Tuple .Split [N , n.type ], Tuple .Split [V , n.type ]] =
417
+ toTuple.splitAt(n)
418
+
419
+ /** The tuple consisting of all elements of this tuple followed by all elements
420
+ * of tuple `that`. The names of the two tuples must be disjoint.
421
+ */
422
+ inline def ++ [N2 <: Tuple , V2 <: Tuple ](that : NamedTuple [N2 , V2 ])(using Tuple .Disjoint [N , N2 ] =:= true )
423
+ : NamedTuple [Tuple .Concat [N , N2 ], Tuple .Concat [V , V2 ]]
424
+ = toTuple ++ that.toTuple
425
+
426
+ // inline def :* [L] (x: L): NamedTuple[Append[N, ???], Append[V, L] = ???
427
+ // inline def *: [H] (x: H): NamedTuple[??? *: N], H *: V] = ???
428
+
429
+ /** The named tuple consisting of all element values of this tuple mapped by
430
+ * the polymorphic mapping function `f`. The names of elements are preserved.
431
+ * If `x = (n1 = v1, ..., ni = vi)` then `x.map(f) = `(n1 = f(v1), ..., ni = f(vi))`.
432
+ */
433
+ inline def map [F [_]](f : [t] => t => F [t]): NamedTuple [N , Tuple .Map [V , F ]] =
434
+ toTuple.map(f).asInstanceOf [NamedTuple [N , Tuple .Map [V , F ]]]
435
+
436
+ /** The named tuple consisting of all elements of this tuple in reverse */
437
+ inline def reverse : NamedTuple [Tuple .Reverse [N ], Tuple .Reverse [V ]] =
438
+ toTuple.reverse
439
+
440
+ /** The named tuple consisting of all elements values of this tuple zipped
441
+ * with corresponding element values in named tuple `that`.
442
+ * If the two tuples have different sizes,
443
+ * the extra elements of the larger tuple will be disregarded.
444
+ * The names of `x` and `that` at the same index must be the same.
445
+ * The result tuple keeps the same names as the operand tuples.
446
+ */
447
+ inline def zip [V2 <: Tuple ](that : NamedTuple [N , V2 ]): NamedTuple [N , Tuple .Zip [V , V2 ]] =
448
+ toTuple.zip(that.toTuple)
449
+
450
+ /** A list consisting of all element values */
451
+ inline def toList : List [Tuple .Union [V ]] = toTuple.toList.asInstanceOf [List [Tuple .Union [V ]]]
452
+
453
+ /** An array consisting of all element values */
454
+ inline def toArray : Array [Object ] = toTuple.toArray
455
+
456
+ /** An immutable array consisting of all element values */
457
+ inline def toIArray : IArray [Object ] = toTuple.toIArray
458
+
459
+ end extension
460
+
461
+ /** The size of a named tuple, represented as a literal constant subtype of Int */
462
+ type Size [X <: AnyNamedTuple ] = Tuple .Size [DropNames [X ]]
463
+
464
+ /** The type of the element value at position N in the named tuple X */
465
+ type Elem [X <: AnyNamedTuple , N <: Int ] = Tuple .Elem [DropNames [X ], N ]
466
+
467
+ /** The type of the first element value of a named tuple */
468
+ type Head [X <: AnyNamedTuple ] = Elem [X , 0 ]
469
+
470
+ /** The type of the last element value of a named tuple */
471
+ type Last [X <: AnyNamedTuple ] = Tuple .Last [DropNames [X ]]
472
+
473
+ /** The type of a named tuple consisting of all elements of named tuple X except the first one */
474
+ type Tail [X <: AnyNamedTuple ] = Drop [X , 1 ]
475
+
476
+ /** The type of the initial part of a named tuple without its last element */
477
+ type Init [X <: AnyNamedTuple ] =
478
+ NamedTuple [Tuple .Init [Names [X ]], Tuple .Init [DropNames [X ]]]
479
+
480
+ /** The type of the named tuple consisting of the first `N` elements of `X`,
481
+ * or all elements if `N` exceeds `Size[X]`.
482
+ */
483
+ type Take [X <: AnyNamedTuple , N <: Int ] =
484
+ NamedTuple [Tuple .Take [Names [X ], N ], Tuple .Take [DropNames [X ], N ]]
485
+
486
+ /** The type of the named tuple consisting of all elements of `X` except the first `N` ones,
487
+ * or no elements if `N` exceeds `Size[X]`.
488
+ */
489
+ type Drop [X <: AnyNamedTuple , N <: Int ] =
490
+ NamedTuple [Tuple .Drop [Names [X ], N ], Tuple .Drop [DropNames [X ], N ]]
491
+
492
+ /** The pair type `(Take(X, N), Drop[X, N]). */
493
+ type Split [X <: AnyNamedTuple , N <: Int ] = (Take [X , N ], Drop [X , N ])
494
+
495
+ /** Type of the concatenation of two tuples `X` and `Y` */
496
+ type Concat [X <: AnyNamedTuple , Y <: AnyNamedTuple ] =
497
+ NamedTuple [Tuple .Concat [Names [X ], Names [Y ]], Tuple .Concat [DropNames [X ], DropNames [Y ]]]
498
+
499
+ /** The type of the named tuple `X` mapped with the type-level function `F`.
500
+ * If `X = (n1 : T1, ..., ni : Ti)` then `Map[X, F] = `(n1 : F[T1], ..., ni : F[Ti])`.
501
+ */
502
+ type Map [X <: AnyNamedTuple , F [_ <: Tuple .Union [DropNames [X ]]]] =
503
+ NamedTuple [Names [X ], Tuple .Map [DropNames [X ], F ]]
504
+
505
+ /** A named tuple with the elements of tuple `X` in reversed order */
506
+ type Reverse [X <: AnyNamedTuple ] =
507
+ NamedTuple [Tuple .Reverse [Names [X ]], Tuple .Reverse [DropNames [X ]]]
508
+
509
+ /** The type of the named tuple consisting of all element values of
510
+ * named tuple `X` zipped with corresponding element values of
511
+ * named tuple `Y`. If the two tuples have different sizes,
512
+ * the extra elements of the larger tuple will be disregarded.
513
+ * The names of `X` and `Y` at the same index must be the same.
514
+ * The result tuple keeps the same names as the operand tuples.
515
+ * For example, if
516
+ * ```
517
+ * X = (n1 : S1, ..., ni : Si)
518
+ * Y = (n1 : T1, ..., nj : Tj) where j >= i
519
+ * ```
520
+ * then
521
+ * ```
522
+ * Zip[X, Y] = (n1 : (S1, T1), ..., ni: (Si, Ti))
523
+ * ```
524
+ * @syntax markdown
525
+ */
526
+ type Zip [X <: AnyNamedTuple , Y <: AnyNamedTuple ] =
527
+ Tuple .Conforms [Names [X ], Names [Y ]] match
528
+ case true =>
529
+ NamedTuple [Names [X ], Tuple .Zip [DropNames [X ], DropNames [Y ]]]
530
+
531
+ type From [T ] <: AnyNamedTuple
532
+
533
+ end NamedTuple
534
+
535
+ /** Separate from NamedTuple object so that we can match on the opaque type NamedTuple. */
536
+ @ experimental
537
+ object NamedTupleDecomposition :
538
+ import NamedTuple .*
539
+
540
+ /** The names of a named tuple, represented as a tuple of literal string values. */
541
+ type Names [X <: AnyNamedTuple ] <: Tuple = X match
542
+ case NamedTuple [n, _] => n
543
+
544
+ /** The value types of a named tuple represented as a regular tuple. */
545
+ type DropNames [NT <: AnyNamedTuple ] <: Tuple = NT match
546
+ case NamedTuple [_, x] => x
547
+ ```
0 commit comments