Suppose I have a class defined as:
case class Box[A](a: A)
I want to write a generic method that unpack tuple (Box[A1](a1), .., Box[An](an))
to a tuple (a1, .., an)
with type (A1, .., An)
.
I tried with Match Types
with no luck:
scala> type Unpack[Bs <: Tuple] <: Tuple = Bs match {
| case Unit => Unit
| case Box[a] *: bs => a *: Unpack[bs]
| }
scala> def unpack[Bs <: Tuple](bs: Bs): Unpack[Bs] = bs match {
| case () => ()
| case Box(a) *: as => a *: unpack(as)
| }
2 | case () => ()
| ^^
|Found: Unit
|Required: Unpack[Bs]
|
|where: Bs is a type in method unpack with bounds >: Unit(?1) | Unit(?2) and <: Tuple
3 | case Box(a) *: as => a *: unpack(as)
| ^^^^^^^^^^^^^^^
|Found: A$1 *: Unpack[Tuple]
|Required: Unpack[Bs]
|
|where: Bs is a type in method unpack with bounds >: (Any *: Tuple)(?3) and <: Tuple
I am guessing that you tested code based on Concat
example from the documentation. I tested this example... and it doesn't compile on 0.21.0-RC1
.
dotr -version
Starting dotty REPL...
Dotty compiler version 0.21.0-RC1 -- Copyright 2002-2019, LAMP/EPFL
scala> type Concat[+Xs <: Tuple, +Ys <: Tuple] <: Tuple = Xs match {
| case Unit => Ys
| case x *: xs => x *: Concat[xs, Ys]
| }
1 |type Concat[+Xs <: Tuple, +Ys <: Tuple] <: Tuple = Xs match {
| ^^^^^^^^^^^^
|covariant type parameter Xs occurs in invariant position in Xs match {
| case Unit => Ys
| case
| [x, xs <: Tuple] => scala.internal.MatchCase[x *: xs, x *: Concat[xs, Ys]]
|} <: Tuple
If I remove that +
to make it similar to your example, type definition passes:
type Concat[Xs <: Tuple, Ys <: Tuple] <: Tuple = Xs match {
| case Unit => Ys
| case x *: xs => x *: Concat[xs, Ys]
| }
scala>
But I am unable to write an implementation:
def concat[Xs <: Tuple, Ys <: Tuple](Xs: Xs, Ys: Ys): Concat[Xs, Ys] = Xs match {
| case () => Ys
| case x *: xs => x *: concat(xs, Ys)
| }
2 | case () => Ys
| ^^
|Found: (Ys : Ys)
|Required: Concat[Xs, Ys]
|
|where: Xs is a type in method concat with bounds >: (?1 : Unit) | (?2 : Unit) and <: Tuple
| Ys is a type in method concat with bounds <: Tuple
3 | case x *: xs => x *: concat(xs, Ys)
| ^^^^^^^^^^^^^^^^^^^
|Found: Any *: Concat[Tuple, Ys]
|Required: Concat[Xs, Ys]
|
|where: Xs is a type in method concat with bounds >: (?3 : Any *: Tuple) and <: Tuple
| Ys is a type in method concat with bounds <: Tuple
So, let's consult the documentation. Thing is... currently there is not documentation how to implement things. There is a section telling us that things can be tricky.
So how it looks in the actual code? Concat
implementation in source code looks currently like this:
def dynamicConcat[This <: Tuple, That <: Tuple](self: This, that: That): Concat[This, That] = {
type Result = Concat[This, That]
// If one of the tuples is empty, we can leave early
(self: Any) match {
case self: Unit => return that.asInstanceOf[Result]
case _ =>
}
(that: Any) match {
case that: Unit => return self.asInstanceOf[Result]
case _ =>
}
val arr = new Array[Object](self.size + that.size)
// Copies the tuple to an array, at the given offset
inline def copyToArray[T <: Tuple](tuple: T, array: Array[Object], offset: Int): Unit = (tuple: Any) match {
case xxl: TupleXXL =>
System.arraycopy(xxl.elems, 0, array, offset, tuple.size)
case _ =>
tuple.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[Object]]
.copyToArray(array, offset, tuple.size)
}
// In the general case, we copy the two tuples to an array, and convert it back to a tuple
copyToArray(self, arr, 0)
copyToArray(that, arr, self.size)
dynamicFromIArray[Result](arr.asInstanceOf[IArray[Object]])
}
Sure, probably someone could argue that is was for performance reasons(?), but it seems that (until some better way will be documented) values for match types can only be implemented in kind of hacky way using .asInstanceOf
. And it is totally on you to make sure that values will match the deduced type (yuk!):
scala> def unpack[Bs <: Tuple](bs: Bs): Unpack[Bs] = bs match {
| case () => ().asInstanceOf[Unpack[Bs]]
| case Box(a) *: as => (a *: unpack(as)).asInstanceOf[Unpack[Bs]]
| }
def unpack[Bs <: Tuple](bs: Bs): Unpack[Bs]
scala> unpack( ( Box(1), Box("test") ) )
val res0: Int *: Unpack[Box[String] *: Unit] = (1,test)
scala>
Hopefully, some Dotty contributor could suggest a better solution, but so far it is the only way I see it's doable.