forked from scala/scala3
-
Notifications
You must be signed in to change notification settings - Fork 17
/
NamedTuple.scala
224 lines (175 loc) · 9.26 KB
/
NamedTuple.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package scala
import compiletime.ops.boolean.*
object NamedTuple:
/** The type to which named tuples get mapped to. For instance,
* (name: String, age: Int)
* gets mapped to
* NamedTuple[("name", "age"), (String, Int)]
*/
opaque type NamedTuple[N <: Tuple, +V <: Tuple] >: V <: AnyNamedTuple = V
/** A type which is a supertype of all named tuples */
opaque type AnyNamedTuple = Any
def apply[N <: Tuple, V <: Tuple](x: V): NamedTuple[N, V] = x
def unapply[N <: Tuple, V <: Tuple](x: NamedTuple[N, V]): Some[V] = Some(x)
/** A named tuple expression will desugar to a call to `build`. For instance,
* `(name = "Lyra", age = 23)` will desugar to `build[("name", "age")]()(("Lyra", 23))`.
*/
inline def build[N <: Tuple]()[V <: Tuple](x: V): NamedTuple[N, V] = x
extension [V <: Tuple](x: V)
inline def withNames[N <: Tuple]: NamedTuple[N, V] = x
export NamedTupleDecomposition.{
Names, DropNames,
apply, size, init, head, last, tail, take, drop, splitAt, ++, map, reverse, zip, toList, toArray, toIArray
}
extension [N <: Tuple, V <: Tuple](x: NamedTuple[N, V])
// ALL METHODS DEPENDING ON `toTuple` MUST BE EXPORTED FROM `NamedTupleDecomposition`
/** The underlying tuple without the names */
inline def toTuple: V = x
// This intentionally works for empty named tuples as well. I think NonEmptyTuple is a dead end
// and should be reverted, just like NonEmptyList is also appealing at first, but a bad idea
// in the end.
// inline def :* [L] (x: L): NamedTuple[Append[N, ???], Append[V, L] = ???
// inline def *: [H] (x: H): NamedTuple[??? *: N], H *: V] = ???
end extension
/** The size of a named tuple, represented as a literal constant subtype of Int */
type Size[X <: AnyNamedTuple] = Tuple.Size[DropNames[X]]
/** The type of the element value at position N in the named tuple X */
type Elem[X <: AnyNamedTuple, N <: Int] = Tuple.Elem[DropNames[X], N]
/** The type of the first element value of a named tuple */
type Head[X <: AnyNamedTuple] = Elem[X, 0]
/** The type of the last element value of a named tuple */
type Last[X <: AnyNamedTuple] = Tuple.Last[DropNames[X]]
/** The type of a named tuple consisting of all elements of named tuple X except the first one */
type Tail[X <: AnyNamedTuple] = Drop[X, 1]
/** The type of the initial part of a named tuple without its last element */
type Init[X <: AnyNamedTuple] =
NamedTuple[Tuple.Init[Names[X]], Tuple.Init[DropNames[X]]]
/** The type of the named tuple consisting of the first `N` elements of `X`,
* or all elements if `N` exceeds `Size[X]`.
*/
type Take[X <: AnyNamedTuple, N <: Int] =
NamedTuple[Tuple.Take[Names[X], N], Tuple.Take[DropNames[X], N]]
/** The type of the named tuple consisting of all elements of `X` except the first `N` ones,
* or no elements if `N` exceeds `Size[X]`.
*/
type Drop[X <: AnyNamedTuple, N <: Int] =
NamedTuple[Tuple.Drop[Names[X], N], Tuple.Drop[DropNames[X], N]]
/** The pair type `(Take(X, N), Drop[X, N]). */
type Split[X <: AnyNamedTuple, N <: Int] = (Take[X, N], Drop[X, N])
/** Type of the concatenation of two tuples `X` and `Y` */
type Concat[X <: AnyNamedTuple, Y <: AnyNamedTuple] =
NamedTuple[Tuple.Concat[Names[X], Names[Y]], Tuple.Concat[DropNames[X], DropNames[Y]]]
/** The type of the named tuple `X` mapped with the type-level function `F`.
* If `X = (n1 : T1, ..., ni : Ti)` then `Map[X, F] = `(n1 : F[T1], ..., ni : F[Ti])`.
*/
type Map[X <: AnyNamedTuple, F[_ <: Tuple.Union[DropNames[X]]]] =
NamedTuple[Names[X], Tuple.Map[DropNames[X], F]]
/** A named tuple with the elements of tuple `X` in reversed order */
type Reverse[X <: AnyNamedTuple] =
NamedTuple[Tuple.Reverse[Names[X]], Tuple.Reverse[DropNames[X]]]
/** The type of the named tuple consisting of all element values of
* named tuple `X` zipped with corresponding element values of
* named tuple `Y`. If the two tuples have different sizes,
* the extra elements of the larger tuple will be disregarded.
* The names of `X` and `Y` at the same index must be the same.
* The result tuple keeps the same names as the operand tuples.
* For example, if
* ```
* X = (n1 : S1, ..., ni : Si)
* Y = (n1 : T1, ..., nj : Tj) where j >= i
* ```
* then
* ```
* Zip[X, Y] = (n1 : (S1, T1), ..., ni: (Si, Ti))
* ```
* @syntax markdown
*/
type Zip[X <: AnyNamedTuple, Y <: AnyNamedTuple] =
Names[X] match
case Names[Y] =>
NamedTuple[Names[X], Tuple.Zip[DropNames[X], DropNames[Y]]]
/** A type specially treated by the compiler to represent all fields of a
* class argument `T` as a named tuple. Or, if `T` is already a named tuple,
* `From[T]` is the same as `T`.
*/
type From[T] <: AnyNamedTuple
/** The type of the empty named tuple */
type Empty = NamedTuple[EmptyTuple, EmptyTuple]
/** The empty named tuple */
val Empty: Empty = EmptyTuple
end NamedTuple
/** Separate from NamedTuple object so that we can match on the opaque type NamedTuple. */
object NamedTupleDecomposition:
import NamedTuple.*
extension [N <: Tuple, V <: Tuple](x: NamedTuple[N, V])
/** The value (without the name) at index `n` of this tuple */
inline def apply(n: Int): Tuple.Elem[V, n.type] =
inline x.toTuple match
case tup: NonEmptyTuple => tup(n).asInstanceOf[Tuple.Elem[V, n.type]]
case tup => tup.productElement(n).asInstanceOf[Tuple.Elem[V, n.type]]
/** The number of elements in this tuple */
inline def size: Tuple.Size[V] = x.toTuple.size
/** The first element value of this tuple */
inline def head: Tuple.Elem[V, 0] = apply(0)
/** The last element value of this tuple */
inline def last: Tuple.Last[V] = apply(size - 1).asInstanceOf[Tuple.Last[V]]
/** The tuple consisting of all elements of this tuple except the last one */
inline def init: NamedTuple[Tuple.Init[N], Tuple.Init[V]] =
x.toTuple.take(size - 1).asInstanceOf[NamedTuple[Tuple.Init[N], Tuple.Init[V]]]
/** The tuple consisting of all elements of this tuple except the first one */
inline def tail: NamedTuple[Tuple.Tail[N], Tuple.Tail[V]] =
x.toTuple.drop(1).asInstanceOf[NamedTuple[Tuple.Tail[N], Tuple.Tail[V]]]
/** The tuple consisting of the first `n` elements of this tuple, or all
* elements if `n` exceeds `size`.
*/
inline def take(n: Int): NamedTuple[Tuple.Take[N, n.type], Tuple.Take[V, n.type]] =
x.toTuple.take(n)
/** The tuple consisting of all elements of this tuple except the first `n` ones,
* or no elements if `n` exceeds `size`.
*/
inline def drop(n: Int): NamedTuple[Tuple.Drop[N, n.type], Tuple.Drop[V, n.type]] =
x.toTuple.drop(n)
/** The tuple `(x.take(n), x.drop(n))` */
inline def splitAt(n: Int):
(NamedTuple[Tuple.Take[N, n.type], Tuple.Take[V, n.type]],
NamedTuple[Tuple.Drop[N, n.type], Tuple.Drop[V, n.type]]) =
// would be nice if this could have type `Split[NamedTuple[N, V]]` instead, but
// we get a type error then. Similar for other methods here.
x.toTuple.splitAt(n)
/** The tuple consisting of all elements of this tuple followed by all elements
* of tuple `that`. The names of the two tuples must be disjoint.
*/
inline def ++ [N2 <: Tuple, V2 <: Tuple](that: NamedTuple[N2, V2])(using Tuple.Disjoint[N, N2] =:= true)
: NamedTuple[Tuple.Concat[N, N2], Tuple.Concat[V, V2]]
= x.toTuple ++ that.toTuple
/** The named tuple consisting of all element values of this tuple mapped by
* the polymorphic mapping function `f`. The names of elements are preserved.
* If `x = (n1 = v1, ..., ni = vi)` then `x.map(f) = `(n1 = f(v1), ..., ni = f(vi))`.
*/
inline def map[F[_]](f: [t] => t => F[t]): NamedTuple[N, Tuple.Map[V, F]] =
x.toTuple.map(f).asInstanceOf[NamedTuple[N, Tuple.Map[V, F]]]
/** The named tuple consisting of all elements of this tuple in reverse */
inline def reverse: NamedTuple[Tuple.Reverse[N], Tuple.Reverse[V]] =
x.toTuple.reverse
/** The named tuple consisting of all elements values of this tuple zipped
* with corresponding element values in named tuple `that`.
* If the two tuples have different sizes,
* the extra elements of the larger tuple will be disregarded.
* The names of `x` and `that` at the same index must be the same.
* The result tuple keeps the same names as the operand tuples.
*/
inline def zip[V2 <: Tuple](that: NamedTuple[N, V2]): NamedTuple[N, Tuple.Zip[V, V2]] =
x.toTuple.zip(that.toTuple)
/** A list consisting of all element values */
inline def toList: List[Tuple.Union[V]] = x.toTuple.toList.asInstanceOf[List[Tuple.Union[V]]]
/** An array consisting of all element values */
inline def toArray: Array[Object] = x.toTuple.toArray
/** An immutable array consisting of all element values */
inline def toIArray: IArray[Object] = x.toTuple.toIArray
end extension
/** The names of a named tuple, represented as a tuple of literal string values. */
type Names[X <: AnyNamedTuple] <: Tuple = X match
case NamedTuple[n, _] => n
/** The value types of a named tuple represented as a regular tuple. */
type DropNames[NT <: AnyNamedTuple] <: Tuple = NT match
case NamedTuple[_, x] => x