From 5fd1c49d8c09bfe70deed2b7853a8e7994f7d921 Mon Sep 17 00:00:00 2001 From: Ivan Klass Date: Mon, 17 Mar 2025 20:07:28 +0100 Subject: [PATCH] Add Tuple.mapKind method --- library/src/scala/Tuple.scala | 10 +++++++++ tests/run/Tuple-mapKind.scala | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 tests/run/Tuple-mapKind.scala diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 57d1572772e2..4578318b9f3c 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -83,6 +83,13 @@ sealed trait Tuple extends Product { inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F] = runtime.Tuples.map(this, f).asInstanceOf[Map[this.type, F]] + /** Given a tuple of form `(F[T1], F[T2], ..., F[Tn])` and a function from F[T] to G[T] for any T out of T1...Tn, + * returns a new tuple `(G[T1], G[T2], ..., G[Tn])`. + */ + inline def mapKind[F[_], G[_]](fk: [t <: Union[InverseMap[this.type, F]]] => F[t] => G[t]): MapKind[this.type, F, G] = + runtime.Tuples.map(this, fk.asInstanceOf).asInstanceOf[MapKind[this.type, F, G]] + + /** Given a tuple `(a1, ..., am)`, returns the tuple `(a1, ..., an)` consisting * of its first n elements. */ @@ -219,6 +226,9 @@ object Tuple { case EmptyTuple => EmptyTuple } + /** Converts a tuple `(F[T1], F[T2], ..., F[Tn])` to `(G[T1], G[T2], ..., G[Tn])` */ + type MapKind[T <: Tuple, F[_], G[_]] = Map[InverseMap[T, F], G] + /** Implicit evidence. IsMappedBy[F][X] is present in the implicit scope iff * X is a tuple for which each element's type is constructed via `F`. E.g. * (F[A1], ..., F[An]), but not `(F[A1], B2, ..., F[An])` where B2 does not diff --git a/tests/run/Tuple-mapKind.scala b/tests/run/Tuple-mapKind.scala new file mode 100644 index 000000000000..680bb2eac3da --- /dev/null +++ b/tests/run/Tuple-mapKind.scala @@ -0,0 +1,39 @@ +object Test { + + type Tup = (Int, Double, Boolean, String) + + def main(args: Array[String]): Unit = { + + val headOption: [x] => Seq[x] => Option[x] = [x] => (seq: Seq[x]) => seq.headOption + val firstTruthy: [T <: Tuple.Union[Tup]] => Seq[T] => Option[T] = [T <: Tuple.Union[Tup]] => + (seq: Seq[T]) => seq.find { + case x: Int => x > 0 + case x: Double => x > 0.0 + case x: Boolean => x + case x: String => x.nonEmpty + } + + val seqTuple: Tuple.Map[Tup, Seq] = (Vector(0, 1), Seq.empty, List(false, true), Vector("4", "44")) + val expectHeadOptionTuple: Tuple.Map[Tup, Option] = (Some(0), None, Some(false), Some("4")) + val expectFirstTruthyTuple: Tuple.Map[Tup, Option] = (Some(1), None, Some(true), Some("4")) + + assert( + EmptyTuple.mapKind[Seq, Option](headOption) == EmptyTuple + ) + assert( + EmptyTuple.mapKind[Seq, Option](firstTruthy) == EmptyTuple + ) + assert( + (List("", "1") *: EmptyTuple).mapKind[Seq, Option](headOption) == (Some("") *: EmptyTuple) + ) + assert( + (List("", "1") *: EmptyTuple).mapKind[Seq, Option](firstTruthy) == (Some("1") *: EmptyTuple) + ) + assert( + seqTuple.mapKind[Seq, Option](headOption) == expectHeadOptionTuple + ) + assert( + seqTuple.mapKind[Seq, Option](firstTruthy) == expectFirstTruthyTuple + ) + } +}