@@ -166,6 +166,17 @@ object Tuple:
166166 case EmptyTuple => Y
167167 case x1 *: xs1 => x1 *: Concat [xs1, Y ]
168168
169+ /** An infix shorthand for `Concat[X, Y]` */
170+ infix type ++ [X <: Tuple , + Y <: Tuple ] = Concat [X , Y ]
171+
172+ /** The index of `Y` in tuple `X` as a literal constant Int,
173+ * or `Size[X]` if `Y` does not occur in `X`
174+ */
175+ type IndexOf [X <: Tuple , Y ] <: Int = X match
176+ case Y *: _ => 0
177+ case x *: xs => S [IndexOf [xs, Y ]]
178+ case EmptyTuple => 0
179+
169180 /** Fold a tuple `(T1, ..., Tn)` into `F[T1, F[... F[Tn, Z]...]]]` */
170181 type Fold [Tup <: Tuple , Z , F [_, _]] = Tup match
171182 case EmptyTuple => Z
@@ -258,6 +269,42 @@ object Tuple:
258269 */
259270 type Union [T <: Tuple ] = Fold [T , Nothing , [x, y] =>> x | y]
260271
272+ /** A type level Boolean indicating whether the tuple `X` conforms
273+ * to the tuple `Y`. This means:
274+ * - the two tuples have the same number of elements
275+ * - for corresponding elements `x` in `X` and `y` in `Y`, `x` matches `y`.
276+ * @pre The elements of `X` are assumed to be singleton types
277+ */
278+ type Conforms [X <: Tuple , Y <: Tuple ] <: Boolean = Y match
279+ case EmptyTuple =>
280+ X match
281+ case EmptyTuple => true
282+ case _ => false
283+ case y *: ys =>
284+ X match
285+ case `y` *: xs => Conforms [xs, ys]
286+ case _ => false
287+
288+ /** A type level Boolean indicating whether the tuple `X` has an element
289+ * that matches `Y`.
290+ * @pre The elements of `X` are assumed to be singleton types
291+ */
292+ type Contains [X <: Tuple , Y ] <: Boolean = X match
293+ case Y *: _ => true
294+ case x *: xs => Contains [xs, Y ]
295+ case EmptyTuple => false
296+
297+ /** A type level Boolean indicating whether the type `Y` contains
298+ * none of the elements of `X`.
299+ * @pre The elements of `X` and `Y` are assumed to be singleton types
300+ */
301+ type Disjoint [X <: Tuple , Y <: Tuple ] <: Boolean = X match
302+ case x *: xs =>
303+ Contains [Y , x] match
304+ case true => false
305+ case false => Disjoint [xs, Y ]
306+ case EmptyTuple => true
307+
261308 /** Empty tuple */
262309 def apply (): EmptyTuple = EmptyTuple
263310
@@ -297,6 +344,31 @@ object Tuple:
297344 def fromProduct (product : Product ): Tuple =
298345 runtime.Tuples .fromProduct(product)
299346
347+ extension [X <: Tuple ](inline x : X )
348+
349+ /** The index (starting at 0) of the first element in the type `X` of `x`
350+ * that matches type `Y`.
351+ */
352+ inline def indexOfType [Y ] = constValue[IndexOf [X , Y ]]
353+
354+ /** A boolean indicating whether there is an element in the type `X` of `x`
355+ * that matches type `Y`.
356+ */
357+
358+ inline def containsType [Y ] = constValue[Contains [X , Y ]]
359+
360+ /* Note: It would be nice to add the following two extension methods:
361+
362+ inline def indexOf[Y: Precise](y: Y) = constValue[IndexOf[X, Y]]
363+ inline def containsType[Y: Precise](y: Y) = constValue[Contains[X, Y]]
364+
365+ because we could then move indexOf/contains completely to the value level.
366+ But this requires `Y` to be inferred precisely, and therefore a mechanism
367+ like the `Precise` context bound used above, which does not yet exist.
368+ */
369+
370+ end extension
371+
300372 def fromProductTyped [P <: Product ](p : P )(using m : scala.deriving.Mirror .ProductOf [P ]): m.MirroredElemTypes =
301373 runtime.Tuples .fromProduct(p).asInstanceOf [m.MirroredElemTypes ]
302374
0 commit comments