Skip to content

Commit 4df97c8

Browse files
committed
Improvements to Tuples
- fromArray now takes optional length, useful when mapping a resizable buffer to a tuple. - New types: IndexOf, Contains, IndicesWhere, ReverseOnto - New methods: filter, indexOfType, containsType, reverseOnto - reverse methods stabilized - New boolean types useful for comparing labels: Conforms, Contains, Disjoint
1 parent 55c2002 commit 4df97c8

File tree

3 files changed

+124
-27
lines changed

3 files changed

+124
-27
lines changed

library/src/scala/Tuple.scala

Lines changed: 113 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package scala
22

3-
import annotation.{experimental, showAsInfix}
3+
import annotation.showAsInfix
44
import compiletime.*
55
import compiletime.ops.int.*
66

@@ -65,7 +65,6 @@ sealed trait Tuple extends Product {
6565
inline def take[This >: this.type <: Tuple](n: Int): Take[This, n.type] =
6666
runtime.Tuples.take(this, n).asInstanceOf[Take[This, n.type]]
6767

68-
6968
/** Given a tuple `(a1, ..., am)`, returns the tuple `(an+1, ..., am)` consisting
7069
* all its elements except the first n ones.
7170
*/
@@ -82,9 +81,22 @@ sealed trait Tuple extends Product {
8281
/** Given a tuple `(a1, ..., am)`, returns the reversed tuple `(am, ..., a1)`
8382
* consisting all its elements.
8483
*/
85-
@experimental
8684
inline def reverse[This >: this.type <: Tuple]: Reverse[This] =
8785
runtime.Tuples.reverse(this).asInstanceOf[Reverse[This]]
86+
87+
/** A tuple with the fields of this tuple in reversed order added in front of `acc` */
88+
inline def reverseOnto[This >: this.type <: Tuple, Acc <: Tuple](acc: Acc): ReverseOnto[This, Acc] =
89+
(this.reverse ++ acc).asInstanceOf[ReverseOnto[This, Acc]]
90+
91+
/** A tuple consisting of all elements of this tuple that have types
92+
* for which the given predicate `P` evaluates to truye.
93+
*/
94+
inline def filter[This >: this.type <: Tuple, P[_] <: Boolean]: Filter[This, P] =
95+
val toInclude = constValueTuple[IndicesWhere[This, P]].toArray
96+
val arr = new Array[Object](toInclude.length)
97+
for i <- 0 until toInclude.length do
98+
arr(i) = this.productElement(toInclude(i).asInstanceOf[Int]).asInstanceOf[Object]
99+
Tuple.fromArray(arr).asInstanceOf[Filter[This, P]]
88100
}
89101

90102
object Tuple {
@@ -133,6 +145,14 @@ object Tuple {
133145
}
134146
}
135147

148+
/** The index of `Y` in tuple `X` as a literal constant Int,
149+
* or `Size[X]` if `Y` does not occur in `X`
150+
*/
151+
type IndexOf[X <: Tuple, Y] <: Int = X match
152+
case Y *: _ => 0
153+
case x *: xs => S[IndexOf[xs, Y]]
154+
case EmptyTuple => 0
155+
136156
/** Literal constant Int size of a tuple */
137157
type Size[X <: Tuple] <: Int = X match {
138158
case EmptyTuple => 0
@@ -175,6 +195,12 @@ object Tuple {
175195
}
176196
}
177197

198+
/** A tuple consisting of those indices `N` of tuple `X` where the predicate `P`
199+
* is true for `Elem[X, N]`. Indices are type level values <: Int.
200+
*/
201+
type IndicesWhere[X <: Tuple, P[_] <: Boolean] =
202+
helpers.IndicesWhereHelper[X, P, 0]
203+
178204
/** Given two tuples, `A1 *: ... *: An * At` and `B1 *: ... *: Bn *: Bt`
179205
* where at least one of `At` or `Bt` is `EmptyTuple` or `Tuple`,
180206
* returns the tuple type `(A1, B1) *: ... *: (An, Bn) *: Ct`
@@ -200,18 +226,13 @@ object Tuple {
200226
*/
201227
type IsMappedBy[F[_]] = [X <: Tuple] =>> X =:= Map[InverseMap[X, F], F]
202228

203-
/** Type of the reversed tuple */
204-
@experimental
205-
type Reverse[X <: Tuple] = Helpers.ReverseImpl[EmptyTuple, X]
229+
/** A tuple with the fields of tuple `X` in reversed order */
230+
type Reverse[X <: Tuple] = ReverseOnto[X, EmptyTuple]
206231

207-
@experimental
208-
object Helpers:
209-
210-
/** Type of the reversed tuple */
211-
@experimental
212-
type ReverseImpl[Acc <: Tuple, X <: Tuple] <: Tuple = X match
213-
case x *: xs => ReverseImpl[x *: Acc, xs]
214-
case EmptyTuple => Acc
232+
/** A tuple with the fields of tuple `X` in reversed order added in front of `Acc` */
233+
type ReverseOnto[X <: Tuple, Acc <: Tuple] <: Tuple = X match
234+
case x *: xs => ReverseOnto[xs, x *: Acc]
235+
case EmptyTuple => Acc
215236

216237
/** Transforms a tuple `(T1, ..., Tn)` into `(T1, ..., Ti)`. */
217238
type Take[T <: Tuple, N <: Int] <: Tuple = N match {
@@ -241,6 +262,42 @@ object Tuple {
241262
*/
242263
type Union[T <: Tuple] = Fold[T, Nothing, [x, y] =>> x | y]
243264

265+
/** A type level Boolean indicating whether the tuple `X` conforms
266+
* to the tuple `Y`. This means:
267+
* - the two tuples have the same number of elements
268+
* - for corresponding elements `x` in `X` and `y` in `Y`, `x` matches `y`.
269+
* @pre The elements of `X` are assumed to be singleton types
270+
*/
271+
type Conforms[X <: Tuple, Y <: Tuple] <: Boolean = Y match
272+
case EmptyTuple =>
273+
X match
274+
case EmptyTuple => true
275+
case _ => false
276+
case y *: ys =>
277+
X match
278+
case `y` *: xs => Conforms[xs, ys]
279+
case _ => false
280+
281+
/** A type level Boolean indicating whether the tuple `X` has an element
282+
* that matches `Y`.
283+
* @pre The elements of `X` are assumed to be singleton types
284+
*/
285+
type Contains[X <: Tuple, Y] <: Boolean = X match
286+
case Y *: _ => true
287+
case x *: xs => Contains[xs, Y]
288+
case EmptyTuple => false
289+
290+
/** A type level Boolean indicating whether the type `Y` contains
291+
* none of the elements of `X`.
292+
* @pre The elements of `X` and `Y` are assumed to be singleton types
293+
*/
294+
type Disjoint[X <: Tuple, Y <: Tuple] <: Boolean = X match
295+
case x *: xs =>
296+
Contains[Y, x] match
297+
case true => false
298+
case false => Disjoint[xs, Y]
299+
case EmptyTuple => true
300+
244301
/** Empty tuple */
245302
def apply(): EmptyTuple = EmptyTuple
246303

@@ -251,12 +308,16 @@ object Tuple {
251308
def unapply(x: EmptyTuple): true = true
252309

253310
/** Convert an array into a tuple of unknown arity and types */
254-
def fromArray[T](xs: Array[T]): Tuple = {
311+
def fromArray[T](xs: Array[T]): Tuple =
312+
fromArray(xs, xs.length)
313+
314+
/** Convert the first `n` elements of an array into a tuple of unknown arity and types */
315+
def fromArray[T](xs: Array[T], n: Int): Tuple = {
255316
val xs2 = xs match {
256317
case xs: Array[Object] => xs
257318
case xs => xs.map(_.asInstanceOf[Object])
258319
}
259-
runtime.Tuples.fromArray(xs2)
320+
runtime.Tuples.fromArray(xs2, n)
260321
}
261322

262323
/** Convert an immutable array into a tuple of unknown arity and types */
@@ -273,13 +334,49 @@ object Tuple {
273334
def fromProduct(product: Product): Tuple =
274335
runtime.Tuples.fromProduct(product)
275336

337+
extension [X <: Tuple](inline x: X)
338+
339+
/** The index (starting at 0) of the first element in the type `X` of `x`
340+
* that matches type `Y`.
341+
*/
342+
inline def indexOfType[Y] = constValue[IndexOf[X, Y]]
343+
344+
/** A boolean indicating whether there is an element in the type `X` of `x`
345+
* that matches type `Y`.
346+
*/
347+
348+
inline def containsType[Y] = constValue[Contains[X, Y]]
349+
350+
/* Note: It would be nice to add the following two extension methods:
351+
352+
inline def indexOf[Y: Precise](y: Y) = constValue[IndexOf[X, Y]]
353+
inline def containsType[Y: Precise](y: Y) = constValue[Contains[X, Y]]
354+
355+
because we could then move indexOf/contains completely to the value level.
356+
But this requires `Y` to be inferred precisely, and therefore a mechanism
357+
like the `Precise` context bound used above, which does not yet exist.
358+
*/
359+
360+
end extension
361+
276362
def fromProductTyped[P <: Product](p: P)(using m: scala.deriving.Mirror.ProductOf[P]): m.MirroredElemTypes =
277363
runtime.Tuples.fromProduct(p).asInstanceOf[m.MirroredElemTypes]
278364

279365
given canEqualEmptyTuple: CanEqual[EmptyTuple, EmptyTuple] = CanEqual.derived
280366
given canEqualTuple[H1, T1 <: Tuple, H2, T2 <: Tuple](
281367
using eqHead: CanEqual[H1, H2], eqTail: CanEqual[T1, T2]
282368
): CanEqual[H1 *: T1, H2 *: T2] = CanEqual.derived
369+
370+
object helpers:
371+
372+
/** Used to implement IndicesWhere */
373+
type IndicesWhereHelper[X <: Tuple, P[_] <: Boolean, N <: Int] <: Tuple = X match
374+
case EmptyTuple => EmptyTuple
375+
case h *: t => P[h] match
376+
case true => N *: IndicesWhereHelper[t, P, S[N]]
377+
case false => IndicesWhereHelper[t, P, S[N]]
378+
379+
end helpers
283380
}
284381

285382
/** A tuple of 0 elements */

library/src/scala/runtime/Tuples.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ object Tuples {
2828
arr
2929
}
3030

31-
def fromArray(xs: Array[Object]): Tuple = xs.length match {
31+
def fromArray(xs: Array[Object], n: Int): Tuple = n match {
3232
case 0 => EmptyTuple
3333
case 1 => Tuple1(xs(0))
3434
case 2 => Tuple2(xs(0), xs(1))
@@ -55,10 +55,15 @@ object Tuples {
5555
case _ => TupleXXL.fromIArray(xs.clone().asInstanceOf[IArray[Object]]).asInstanceOf[Tuple]
5656
}
5757

58-
def fromIArray(xs: IArray[Object]): Tuple =
59-
if (xs.length <= 22) fromArray(xs.asInstanceOf[Array[Object]])
58+
def fromArray(xs: Array[Object]): Tuple = fromArray(xs, xs.length)
59+
60+
def fromIArray(xs: IArray[Object], n: Int): Tuple =
61+
if n <= 22 || n != xs.length
62+
then fromArray(xs.asInstanceOf[Array[Object]], n)
6063
else TupleXXL.fromIArray(xs).asInstanceOf[Tuple]
6164

65+
def fromIArray(xs: IArray[Object]): Tuple = fromIArray(xs, xs.length)
66+
6267
def fromProduct(xs: Product): Tuple = (xs.productArity match {
6368
case 0 => EmptyTuple
6469
case 1 =>
@@ -505,7 +510,6 @@ object Tuples {
505510
}
506511
}
507512

508-
@experimental
509513
def reverse(self: Tuple): Tuple = (self: Any) match {
510514
case xxl: TupleXXL => xxlReverse(xxl)
511515
case _ => specialCaseReverse(self)

tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,9 @@ val experimentalDefinitionInLibrary = Set(
9191
"scala.quoted.Quotes.reflectModule.TermParamClauseMethods.erasedArgs",
9292
"scala.quoted.Quotes.reflectModule.TermParamClauseMethods.hasErasedArgs",
9393

94-
// New feature: reverse method on Tuple
95-
"scala.Tuple.reverse",
96-
"scala.Tuple$.Helpers",
97-
"scala.Tuple$.Helpers$",
98-
"scala.Tuple$.Helpers$.ReverseImpl",
99-
"scala.Tuple$.Reverse",
100-
"scala.runtime.Tuples$.reverse"
94+
// New feature: named tuples
95+
"scala.NamedTuple",
96+
"scala.NamedTuple$",
10197
)
10298

10399

0 commit comments

Comments
 (0)