了解我的Scala代码中的协方差

时间:2019-01-11 20:04:12

标签: scala apache-spark covariance contravariance

我正在为以下问题研究正确的语法和结构。

我有两个具有两个独立模式的数据集-分别称为ClientEventServerEvent –存储在磁盘上。我正在使用的代码库定义了一个类Reader[T :< Asset],其中ClientEventServerEventAsset的子类型。 Asset是一个特征。

我正在编写一个函数:

def getPathAndReader(config): (String, Reader[Asset]) = {
    if (config.readClient) {
        return getClientPathAndReader(config)
    } else {
        return getServerPathAndReader(config)
    } 
}

这不能在我的Scala代码中编译。据我了解,T必须是Asset的子类型,ServerEventClientEvent均为Reader[ServerEvent] <: Reader[Asset]。但是由于函数的输入是协变的,所以我编写的函数不能仅返回此较低类型,我必须将其转换为超类型吗?那会丢失太多信息吗?

load是关于特征Asset

的函数
trait Reader[T <: Asset] {
  def load(raw: DataFrame): Dataset[T]
}

构造此代码的另一种方法是什么?

该代码的目的是获取返回的文件路径,并调用Reader::load(filePath: String)以取回数据。子类型的读取器具有一些内部逻辑,用于清除从磁盘检索到的数据,然后将其作为Dataframe返回。这意味着它依赖于传入的类型。我来自C ++ / C#背景,因此我的想法是,如果您有通用的Reader[Asset]但调用Reader::load(path: String),它将基于实际的类型,类似于Base* ptr并调用派生方法。

1 个答案:

答案 0 :(得分:2)

您声称 “据我了解,T必须是Asset的子类型,因此ServerEventClientEvent都是Reader[ServerEvent] <: Reader[Asset]。” 不正确。通常,如果AB是普通类型,例如A <: BG[T]是泛型类型,则所有3种情况都是可能的:

  • 协变案例G[A] <: G[B]-典型的例子是一些只读集合,例如Iterator
  • 反例G[A] :> G[B]-典型示例是某种使用者,例如函数T => ()
  • G[A]G[B]不相关的不变情况。 T的某些用法是协变而某些是协变的最典型情况。例如,简单的映射函数T => T是不变的。同样,大多数可变集合也是不变的,因为“产生”和“消耗”两个对象。

不幸的是,Dataset[T]是不变的(而不是协变Dataset[+T]或协变Dataset[-T])。这实际上使您的Reader也不变。至于如何解决此问题,很难在不了解更大范围的情况下提出建议。例如,为什么您的getClientPathAndReadergetServerPathAndReader不返回Dataset[Asset]?如果确实使用了特定的ServerEventClientEvent,那么您的设计无论如何都不是类型安全的。如果仅使用Asset,则更改读者以返回Dataset[Asset]似乎是最简单的解决方案。