“trait ValueHolder { type ValueType }”和“trait ValueHolder[T] {}”有什么区别?

3
当我阅读Liftweb的源代码时,我发现了一些trait声明:
trait ValueHolder {
  type ValueType
  def get: ValueType
}

trait PValueHolder[T] extends ValueHolder {
  type ValueType = T
}

我的问题是,对于以下两个特征声明:

trait ValueHolder {
    type ValueType
}

trait ValueHolder[T] {
}

我认为它们彼此相等,但它们之间是否有区别呢?其中一个可以做或提供另一个不能做的事情吗?

1个回答

6

第一个称为抽象类型成员,第二个则是Java泛型的密切类比,但并不完全相同。这是实现相同目标的两种不同方式。正如Martin Odersky在他的访谈中所解释的那样,同时拥有抽象类型成员和泛型类型参数的原因之一是正交性:

在抽象概念中一直存在着两个概念:参数化和抽象成员。在Java中,您也具有这两个概念,但它取决于您正在进行抽象的内容。在Java中,您具有抽象方法,但无法将方法作为参数传递。您没有抽象字段,但可以将值作为参数传递。同样,您没有抽象类型成员,但可以将类型指定为参数。因此,在Java中,您也拥有所有这三个概念,但是对于不同的事物,您可以使用不同的抽象原则。您可以认为这种区别相当武断。
在Scala中,我们尝试更加完整和正交。我们决定对所有三种成员使用相同的构造原则。因此,您可以像值参数一样拥有抽象字段。您可以将方法(或“函数”)作为参数传递,或者您可以对它们进行抽象。您可以将类型指定为参数,或者您可以对它们进行抽象。从概念上讲,我们可以将一个模型表示为另一个模型。至少在原则上,我们可以将每种形式的参数化表达为面向对象的抽象形式。因此,在某种意义上,您可以说Scala是一种更加正交和完整的语言。
他还描述了抽象类型成员和泛型类型参数之间的一个差异,这在实践中可以体现出来:

但是,在实践中,当你使用类型参数化与许多不同的东西时,它会导致参数的爆炸,通常还在参数边界内。在1998年的ECOOP会议上,Kim Bruce、Phil Wadler和我发表了一篇论文,在其中我们展示了随着你增加不知道的事情的数量,典型的程序将呈二次增长。所以有非常好的理由不要使用参数,而是有这些抽象成员,因为它们不会给你这个二次的膨胀。

我认为比尔·维纳斯(ScalaTest的创造者)提供的例子非常好且易于理解:

// Type parameter version
trait FixtureSuite[F] {
  // ...
}

// Type member version
trait FixtureSuite {
  type F
  // ...
}

无论哪种情况,F都将是传递到测试中的夹具参数的类型,套件子类将使其具体化。以下是一个需要将StringBuilder传递到每个测试中的具体测试套件的示例,使用类型参数方法:
// Type parameter version
class MySuite extends FixtureSuite[StringBuilder] {
  // ...
}

以下是一个需要将StringBuilder传递到每个测试中的具体测试套件示例,使用抽象类型成员方法实现:
// Type member version
class MySuite extends FixtureSuite {
  type F = StringBuilder
  // ...
}

例如,如果您想将三个不同的fixture对象传递到测试中,您可以这样做,但需要为每个参数指定一个类型。因此,选择了类型参数方法,您的套件类可能会看起来像这样:
// Type parameter version
class MySuite extends FixtureSuite3[StringBuilder, ListBuffer, Stack] with MyHandyFixture {
  // ...
}

相比之下,使用类型成员的方法将会是这样:

// Type member version
class MySuite extends FixtureSuite3 with MyHandyFixture {
  // ...
}

这表明了在实现模块化抽象的目标上有两种方法。更多关于这个话题的内容可以在this可扩展组件的传奇论文中阅读。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接