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