I'm trying to zip tuples together and use match types to get the exact type of the resulting zip. I have a match type and the function:
type Z[A <: Tuple, B <: Tuple] <: Tuple = (A, B) match
case (EmptyTuple, EmptyTuple) => EmptyTuple
case (a *: as, b *: bs) => (a, b) *: Z[as, bs]
def z[A <: Tuple, B <: Tuple](a: A, b: B): Z[A, B] = (a, b) match
case (EmptyTuple, EmptyTuple) => EmptyTuple
case (ah *: at, bh *: bt) => (ah, bh) *: z(at, bt)
However, both cases in z()
result in an error:
Found: EmptyTuple.type Required: test.Tuples.Z[A, B]
and Found: (Any, Any) *: test.Tuples.Z[Tuple, Tuple] Required: test.Tuples.Z[A, B]
, respectively. I would have guessed this would be a pretty straightforward application of match types, but clearly I'm wrong. What am I missing here?
I would also like to restrict both the match type and the function z()
to tuples that have the same length (like how shapeless' Length
could be used in scala 2), but perhaps that is a separate question.
EDIT:
I managed to get the function z()
working with explicit casting, but I still think there has to be a way to avoid that:
def z[A <: Tuple, B <: Tuple, M <: Int](a: A, b: B): Z[A, B] = (a, b) match
case (ah *: at, bh *: bt) => ((ah, bh) *: z(at, bt)).asInstanceOf[Z[A, B]]
case (EmptyTuple, EmptyTuple) => EmptyTuple.asInstanceOf[Z[A, B]]
Also, I was able to get the length aspect working for the function z()
as well, but I would love to know if a) there is a cleaner/less verbose way to achieve this (perhaps without the need to define L
) and b) if there's a way to restrict the type arguments to Z
to be tuples of the same length:
type L[T <: Tuple] <: Int = T match
case EmptyTuple => 0
case _ *: t => 1 + L[t]
type Z[A <: Tuple, B <: Tuple] <: Tuple = (A, B) match
case (EmptyTuple, EmptyTuple) => EmptyTuple
case (a *: as, b *: bs) => (a, b) *: Z[as, bs]
def z[A <: Tuple, B <: Tuple, M <: Int](a: A, b: B)(using L[A] =:= L[B]): Z[A, B] = (a, b) match
case (ah *: at, bh *: bt) => ((ah, bh) *: z(at, bt)).asInstanceOf[Z[A, B]]
case (EmptyTuple, EmptyTuple) => EmptyTuple.asInstanceOf[Z[A, B]]
println(z(1 *: true *: EmptyTuple, "seven" *: 9.8 *: EmptyTuple)) // <-- correctly zips tuples: ((1,seven),(true,9.8))
// println(z(1 *: EmptyTuple, "seven" *: 9.8 *: EmptyTuple)) // <-- results in compile-time error as desired: "Cannot prove that test.Tuples.L[(Int *: EmptyTuple.type)] =:= test.Tuples.L[(String, Double)]..."
EDIT 2:
So actually it turns out that the tuple lengths are already restricted to be equal, as otherwise the match type Z
does not resolve, so I guess the question is just how to avoid the casts in z()
.