Skip to content

Commit e975903

Browse files
committed
Address review comments
- Clarify definition of From - Add section on computed fields - Add NamedTuples.scala in appendix
1 parent 7f4b231 commit e975903

File tree

1 file changed

+249
-2
lines changed

1 file changed

+249
-2
lines changed

content/named-tuples.md

Lines changed: 249 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,47 @@ The translation of named tuples to instances of `NamedTuple` is fixed by the spe
146146
- All tuple operations also work with named tuples "out of the box".
147147
- Macro libraries can rely on this expansion.
148148

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+
149190
### The NamedTuple.From Type
150191

151192
The `NamedTuple` object contains a type definition
@@ -154,15 +195,19 @@ The `NamedTuple` object contains a type definition
154195
```
155196
`From` is treated specially by the compiler. When `NamedTuple.From` is applied to
156197
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
158200
```scala
159201
case class City(zip: Int, name: String, population: Int)
160202
```
161203
then `NamedTuple.From[City]` is the named tuple
162204
```scala
163205
(zip: Int, name: String, population: Int)
164206
```
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`.
166211

167212
### Pattern Matching with Named Fields in General
168213

@@ -183,6 +228,9 @@ This revives SIP 43, with a much simpler desugaring than originally proposed.
183228
Named patterns are compatible with extensible pattern matching simply because
184229
`unapply` results can be named tuples.
185230

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.
186234

187235
### Restrictions
188236

@@ -298,3 +346,202 @@ This section should list prior work related to the proposal, notably:
298346

299347
## FAQ
300348

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

Comments
 (0)