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

我认为它们彼此相等,但是它们之间有什么区别吗?一个人能做或提供另一个人不能做的事吗?

4

1 回答 1

6

第一个被称为abstract type member,第二个与 Java 泛型非常相似,但并不完全相同。这是实现同一目标的两种不同方式。正如 Martin Odersky 在他的采访中解释的那样,同时拥有抽象类型成员和泛型类型参数的一个原因是正交性:

一直有两个抽象概念:参数化和抽象成员。在 Java 中,你也有两者,但这取决于你抽象的内容。在 Java 中,您有抽象方法,但不能将方法作为参数传递。您没有抽象字段,但可以将值作为参数传递。同样,您没有抽象类型成员,但您可以将类型指定为参数。所以在Java中你也有这三个,但是对于你可以使用什么样的抽象原则是有区别的。你可以争辩说这种区别是相当武断的。

我们在 Scala 中所做的就是尝试变得更加完整和正交。我们决定对所有三种成员都采用相同的构造原则。因此,您可以拥有抽象字段以及值参数。您可以将方法(或“函数”)作为参数传递,也可以对它们进行抽象。您可以将类型指定为参数,也可以对它们进行抽象。我们从概念上得到的是,我们可以根据另一个建模一个。至少在原则上,我们可以将每一种参数化表示为一种面向对象的抽象形式。所以在某种意义上你可以说 Scala 是一种更加正交和完整的语言。

他还描述了抽象类型成员和泛型类型参数之间可以在实践中出现的区别:

但在实践中,当你对许多不同的东西使用类型参数化时,它会导致参数的爆炸式增长,而且通常,更重要的是,在参数的范围内。在 1998 年的 ECOOP 上,Kim Bruce、Phil Wadler 和我发表了一篇论文,其中我们展示了当你增加你不知道的事物的数量时,典型的程序将呈二次方增长。所以有很好的理由不做参数,而是拥有这些抽象成员,因为它们不会给你这种二次爆炸。

我认为Bill Veners(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
  // ...
}

例如,如果您想将三个不同的夹具对象传递给测试,您可以这样做,但您需要指定三种类型,每个参数一个。因此选择了类型参数方法,您的套件类可能最终看起来像这样:

// Type parameter version
class MySuite extends FixtureSuite3[StringBuilder, ListBuffer, Stack] with MyHandyFixture {
  // ...
}

而使用类型成员方法,它将如下所示:

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

因此,这显示了实现出色模块化抽象目标的两种方法。有关此主题的更多信息,请参阅这篇关于可扩展组件的传奇论文

于 2013-07-11T06:45:04.300 回答