Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unpack the type of elements in tuple in dotty?

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
like image 389
esse Avatar asked Jan 02 '20 14:01

esse


1 Answers

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.

like image 62
Mateusz Kubuszok Avatar answered Nov 20 '22 10:11

Mateusz Kubuszok