【问题标题】:Scala: Casting results of groupBy(_.getClass)Scala:groupBy(_.getClass) 的转换结果
【发布时间】:2018-02-26 20:58:58
【问题描述】:

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

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 &lt;: BatchableOp[T]]。

如何让 Scala 编译器确信 group 是同一类型(BatchableOp 的子类)?

【问题讨论】:

  • 对不起,我不得不放弃。 scala 中的 f-bound + 破碎的存在类型很难驯服。
  • @HuStmpHrrr 为什么“坏了”?您是否至少设法使编译器崩溃,或者您只是不喜欢存在主义? ;) 存在主义主要是“破碎”for the higher kinds,但我无法让它在这个问题上崩溃:]
  • @AndreyTyukin 从这个意义上说并没有坏掉。首先,存在类型优于协变/反变类型是多余的。在不变的情况下,类型参数上的存在类型非常无用,因为缺乏安全恢复信息类型的方法;但是,编写私有类型变量确实更有意义。如您的答案所示,它在语法上也被破坏了。我个人不喜欢存在类型,因为我早期使用它的经验很糟糕,我意识到每当我想使用它时,我并不真的需要它。
  • 当您编写一个完全通用的框架时,它们在极少数情况下很有用,并且在某些时候必须将类型化的高级构造分解成一堆不同类型的小块,然后压扁进入某种较低级别调度程序的队列。由于 OP 在这里批处理作业,因此存在主义的出现似乎是适当的,甚至是不可避免的。但是,是的,在应用程序的不那么低级别的部分中,您并不经常需要它们。

标签: scala


【解决方案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 的向量,它们的类型是未知的,并且可以完全不同”,而第二种类型说:“它是一个未知类型的 batchOps 向量T,但在至少我们知道它们都是一样的”。

您想要的是类似于以下假设的语言结构:

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

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

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

给定一个Vector[BatchOp[X] forSome X],我知道有一个常见的类型C,它们实际上都是Vector[C],这个C的范围在哪里作为一个可用的类型变量?

原来BatchableOp[C] 本身在作用域中有一个类型变量C。因此,我可以在BachableOp[C] 中添加一个方法collectSameClassInstances,这个方法实际上将有一些类型C 可用,它可以在返回类型中使用。然后我可以立即将collectSameClassInstances 的结果传递给resolve 方法,然后我得到一个完全良性的Vector[Result] 类型作为输出。

最后说明:如果您决定编写任何具有 F 界多态性和存在性的代码,至少要确保您已经非常清楚地记录了究竟是什么 你在那里做的,你将如何确保这种组合不会在代码库的任何其他部分逃逸。将这样的接口暴露给用户感觉不是一个好主意。保持本地化,确保这些抽象不会泄漏到任何地方。

【讨论】:

    【解决方案2】:

    Andrey 的回答有一个关键见解,即具有适当类型变量的唯一范围是 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)
        }
    }
    

    【讨论】:

    • 好像减的有点多? Vector[] 无法编译。我猜你的意思是Vector[BatchableOp[_]]?确实,那时它有点短,并且不需要存在主义。
    • 好收获。还有另一种选择,unsafeResolve 使用collect 方法来匹配类型T,但您需要一个ClassTag 实例来绕过类型擦除。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-03
    相关资源
    最近更新 更多