6

假设我有一个类定义为:

case class Box[A](a: A)

我想编写一个通用方法,将 tuple 解压缩为type(Box[A1](a1), .., Box[An](an))的 tuple 。(a1, .., an)(A1, .., An)

我试过Match Types没有运气:

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
4

1 回答 1

5

我猜您是根据文档Concat中的示例测试代码的。我测试了这个例子......它没有在.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

如果我将其删除+以使其与您的示例相似,则类型定义将通过:

type Concat[Xs <: Tuple, Ys <: Tuple] <: Tuple = Xs match {
     |   case Unit => Ys
     |   case x *: xs => x *: Concat[xs, Ys]
     | }

scala>

但我无法编写实现:

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


所以,让我们查阅文档。事情是......目前没有文档如何实现。有一节告诉我们事情可能很棘手

那么它在实际代码中的外观如何?Concat源代码中的实现目前看起来像这样

 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]])
  }

当然,可能有人会争辩说这是出于性能原因(?),但似乎(直到记录一些更好的方法)匹配类型的值只能以一种 hacky 的方式使用.asInstanceOf. 并且完全由您确保值与推导的类型匹配(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>

希望一些 Dotty 贡献者可以提出更好的解决方案,但到目前为止,这是我认为可行的唯一方法。

于 2020-01-04T01:09:14.267 回答