Scala:转换groupBy的结果(_。getClass)

时间:2018-02-25 23:04:56

标签: scala

在这个假设中,我有一个要执行的操作列表。如果可以将它们一起批处理(例如,从数据库中的同一表中查找不同的行),则该列表中的一些操作将更有效。

trait Result
trait BatchableOp[T <: BatchableOp[T]] {
  def resolve(batch: Vector[T]): Vector[Result]
}

这里我们使用F-bounded Polymorphism来允许操作的实现引用它自己的类型,这非常方便。

然而,这在执行时会出现问题:

def execute(operations: Vector[BatchableOp[_]]): Vector[Result] = {
  def helper[T <: BatchableOp[T]](clazz: Class[T], batch: Vector[T]): Vector[Result] =
    batch.head.resolve(batch)

  operations
    .groupBy(_.getClass)
    .toVector
    .flatMap { case (clazz, batch) => helper(clazz, batch)}
}

这会导致编译器错误,指出inferred type arguments [BatchableOp[_]] do not conform to method helper's type parameter bounds [T <: BatchableOp[T]]

Scala编译器如何确信group是所有相同的类型(BatchableOp的子类)?

2 个答案:

答案 0 :(得分:1)

我想系统地处理这个问题,以便在类似情况下应用相同的解决方案策略。

首先,一个明显的评论:你想要使用矢量。载体的内容可以是不同类型的。矢量的长度不受限制。向量的条目类型的数量不受限制。因此,编译器无法在编译时证明所有内容:您必须在某些时候使用类似asInstanceOf的内容。

现在解决实际问题:

这里编译2.12.4:

import scala.language.existentials

trait Result

type BOX = BatchableOp[X] forSome { type X <: BatchableOp[X] }

trait BatchableOp[C <: BatchableOp[C]] {
  def resolve(batch: Vector[C]): Vector[Result]

  // not abstract, needed only once!
  def collectSameClassInstances(batch: Vector[BOX]): Vector[C] = {
    for (b <- batch if this.getClass.isAssignableFrom(b.getClass))
    yield b.asInstanceOf[C]
  }

  // not abstract either, no additional hassle for subclasses!
  def collectAndResolve(batch: Vector[BOX]): Vector[Result] = 
    resolve(collectSameClassInstances(batch))
}

def execute(operations: Vector[BOX]): Vector[Result] = {

  operations
    .groupBy(_.getClass)
    .toVector
    .flatMap{ case (_, batch) =>
      batch.head.collectAndResolve(batch)
    }
}

我在这里看到的主要问题是在Scala中(与一些实验性依赖类型语言不同),没有简单的方法来“在存在类型的假设下”写下复杂的计算。 因此,似乎很难/不可能转换

Vector[BatchOp[T] forSome T]

进入

Vector[BatchOp[T]] forSome T

这里,第一种类型说:“它是batchOps的向量,它们的类型是未知的,并且可以完全不同”,而第二种类型则表示:“它是未知类型T的batchOps的向量,但至少我们知道它们都是一样的。“

你想要的是类似下面的假设语言结构:

val vec1: Vector[BatchOp[T] forSome T] = ???
val vec2: Vector[BatchOp[T]] forSome T = 
  assumingExistsSomeType[C <: BatchOp[C]] yield {
    /* `C` now available inside this scope `S` */
    vec1.map(_.asInstanceOf[C])
  }

不幸的是,对于存在类型我们没有类似的东西,我们不能在某个范围C中引入辅助类型S,这样当C被消除时,我们剩下一个存在主义(至少我没有看到一般的方法)。

因此,这里唯一需要回答的有趣的问题是:

  

鉴于Vector[BatchOp[X] forSome X]我知道有一个常见类型C ,所以它们实际上都是Vector[C],其中的范围是这个C是否作为可用的类型变量出现?

事实证明BatchableOp[C]本身在范围内有一个类型变量C。因此,我可以向collectSameClassInstances添加方法BachableOp[C],并且此方法实际上可以使用某种类型C,它可以在返回类型中使用。然后我可以立即将collectSameClassInstances的结果传递给resolve方法,然后输出一个完全良性的Vector[Result]类型。

最后评论:如果您决定使用F-bounded多态性存在来编写任何代码,至少要确保您已经非常清楚地记录了的确切内容您正在那里,以及如何确保此组合不会在代码库的任何其他部分中逃脱。将这些接口暴露给用户并不是一个好主意。保持本地化,确保这些抽象不会泄漏到任何地方。

答案 1 :(得分:1)

安德烈的答案有一个关键的见解,即唯一具有相应类型变量的范围在BatchableOp本身。这是一个不依赖于导入existentials的简化版本:

trait Result
trait BatchableOp[T <: BatchableOp[T]] {
  def resolve(batch: Vector[T]): Vector[Result]
  def unsafeResolve(batch: Vector[BatchableOp[_]]): Vector[Result] = {
    resolve(batch.asInstanceOf[Vector[T]])
  }
}

def execute(operations: Vector[BatchableOp[_]]): Vector[Result] = {
  operations
    .groupBy(_.getClass)
    .toVector
    .flatMap{ case (_, batch) =>
      batch.head.unsafeResolve(batch)
    }
}