在这个假设中,我有一个要执行的操作列表。如果可以将它们一起批处理(例如,从数据库中的同一表中查找不同的行),则该列表中的一些操作将更有效。
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
的子类)?
execute
类型后不必更新BatchableOp
方法。答案 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)
}
}