当我阅读Liftweb的源代码时,我发现了一些特征声明:
trait ValueHolder {
type ValueType
def get: ValueType
}
trait PValueHolder[T] extends ValueHolder {
type ValueType = T
}
我的问题是,对于以下两个特征声明:
trait ValueHolder {
type ValueType
}
trait ValueHolder[T] {
}
我认为他们彼此相同,但他们之间有什么区别吗?一个人可以做或提供另一个人不能做的事吗?
答案 0 :(得分:6)
第一个叫做abstract type member
,第二个叫做Java泛型,但不完全相同。这是实现相同目标的两种不同方式。正如Martin Odersky所解释的那样his采访,同时具有抽象类型成员和泛型类型参数的一个原因是正交性:
总有两种抽象概念:参数化 和抽象的成员。在Java中你也有两者,但它取决于 你在抽象的东西。在Java中,您有抽象方法,但是 您不能将方法作为参数传递。你没有抽象 字段,但您可以将值作为参数传递。和你一样 没有抽象类型成员,但您可以将类型指定为 参数。所以在Java中你也有这三个,但有一个 区分你可以用什么抽象原则 各种各样的事情。你可以说这种区别是公平的 任意的。
我们在Scala中所做的是尝试更完整和正交。我们 决定对所有三种类型都有相同的构造原则 成员。所以你可以有抽象的字段和价值 参数。您可以将方法(或“函数”)作为参数传递,或者 你可以抽象出来。您可以将类型指定为参数,或 你可以抽象出来。我们从概念上得到的是我们 可以用另一个来模拟一个。至少在原则上,我们可以 将各种参数化表达为面向对象的一种形式 抽象。所以在某种意义上你可以说Scala更正交 和完整的语言。
他还描述了抽象类型成员和通用类型参数之间的区别,这些参数可以在实践中显示出来:
但实际上,当你使用很多类型参数化时 不同的东西,它会导致参数爆炸,通常, 更重要的是,在参数范围内。在1998年的ECOOP,Kim Bruce, Phil Wadler和我有一篇论文,我们展示了随着你的增加 你不知道的事情数量,典型的程序会增长 二次。所以有很好的理由不做参数, 但要拥有这些抽象成员,因为他们不会给你这个 二次爆炸。
我认为比尔维纳斯(ScalaTest的创建者)given是一个很好的例子:
// Type parameter version
trait FixtureSuite[F] {
// ...
}
和
// Type member version
trait FixtureSuite {
type F
// ...
}
在任何一种情况下,F都是要传递给测试的fixture参数的类型,哪些套件子类将具体化。下面是一个具体测试套件的示例,需要使用类型参数方法将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关于可扩展组件的传奇文章