我在阅读Scala时也有同样的问题。

使用泛型的优点是可以创建一系列类型。没有人需要子类化__abc0——他们可以使用Buffer[Any]Buffer[String]等。

如果您使用抽象类型,那么人们将被迫创建一个子类。人们将需要诸如AnyBufferStringBuffer等类。

你需要决定哪一种更适合你的特殊需要。

你在这个问题上的观点很好:

< p > Scala类型系统的目的 < br > 对话Martin Odersky(第三部分)
(2009年5月18日)

更新(2009年10月):下面的内容实际上已经在Bill Venners的这篇新文章中说明了:
Scala中的抽象类型成员与泛型类型参数(见末尾摘要)

. Scala中的抽象类型成员与泛型类型参数(见末尾摘要

(以下是2009年5月第一次采访的相关摘录,重点是我的)

一般原则

抽象一直有两个概念:

  • 参数化和
  • 抽象的成员。
在Java中,两者都有,但这取决于你对什么进行抽象 在Java中有抽象方法,但不能将方法作为参数传递 你没有抽象字段,但是你可以传递一个值作为参数 类似地,你没有抽象类型成员,但你可以将类型指定为形参 在Java中你也有这三种,但是对于什么抽象原则可以用于什么类型的东西是有区别的。你可能会说,这种区分是相当武断的

Scala之路

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

为什么?

具体来说,我们之前讨论过的抽象类型是处理这些协方差问题的好方法 一个已经存在很长时间的标准问题是动物和食物的问题 问题是要有一个类Animal,它有一个方法eat,这个方法吃一些食物 问题是,如果我们子类化动物,并有一个类,如牛,那么他们将只吃草,而不是任意的食物。例如,牛不能吃鱼 你想要的是能够说牛有一个eat方法,它只吃草,不吃其他东西 实际上,在Java中你不能这样做,因为你可能会构造不合理的情况,就像我前面谈到的将Fruit分配给Apple变量的问题

答案是在Animal类中添加一个抽象类型.
. 0 你说,我的新Animal类有一个类型SuitableFood,我不知道 所以这是一个抽象类型。不需要给出类型的实现。然后你有一个eat方法,它只吃SuitableFood.
然后在Cow类中,我会说,OK,我有一个Cow,它扩展了类Animal,并且对于Cow type SuitableFood equals Grass.
所以抽象类型在我不知道的超类中提供了这种类型的概念,然后我在子类中填充我知道的东西。< / p >

参数化也一样吗?

你的确可以。你可以用它吃的食物的种类来参数化动物类 但是在实践中,当你用许多不同的东西来做这件事时,它会导致参数的爆炸,而且通常在参数界限.
在1998年的ECOOP上,Kim Bruce, Phil Wadler和我发表了一篇论文,其中我们展示了当你增加你不知道的东西的数量时,典型的程序会成倍增长.
. 所以有很好的理由不使用参数,而是使用这些抽象的成员,因为它们不会给你这个二次函数。< / p >

thatismatt在评论中问道:

你认为以下总结公平吗?

  • 抽象类型用于'has-a'或'uses-a'关系(例如Cow eats Grass)
  • 泛型通常是“of”关系(例如List of Ints)
我不确定使用抽象类型或泛型之间的关系是不同的。 不同之处在于:

  • 它们是如何使用的
  • 如何管理参数边界。

为了理解Martin所说的“参数的爆炸,通常,更重要的是,在参数界限中”,以及当抽象类型使用泛型建模时它的后续二次增长,您可以考虑由…Martin Odersky和Matthias Zenger的OOPSLA 2005,在Palcom项目的出版物中引用(完成于2007年)。

相关的提取

定义

抽象类型成员提供了一种灵活的方法来抽象具体类型的组件 抽象类型可以隐藏关于组件内部的信息,类似于它们在< >强SML < / >强签名中的使用。在面向对象的框架中,类可以通过继承进行扩展,它们也可以用作灵活的参数化手段(通常称为族多态性,参见此例如,Weblog条目和由埃里克·恩斯特编写的论文)

注意:家族多态性已被提出用于面向对象语言,作为支持可重用但类型安全的相互递归类的解决方案 族多态性的一个关键思想是族的概念,它用于对相互递归的类进行分组)

有界类型抽象

abstract class MaxCell extends AbsCell {
type T <: Ordered { type O = T }
def setMax(x: T) = if (get < x) set(x)
}
这里,T的类型声明受到一个上类型界限的约束由类名Ordered和细分{ type O = T }组成 上界将子类中T的特化限制为Ordered的子类型,其类型成员O属于equals T.
由于这个约束,Ordered类的<方法保证适用于接收器和类型为t的参数 这个例子表明,有界类型成员本身可以作为有界的一部分出现 (即Scala支持F-bounded多态性)

(注,来自Peter Canning, William Cook, Walter Hill, Walter Olthoff论文:
有界量化是由Cardelli和Wegner引入的,作为对给定类型的所有子类型统一操作的函数类型的一种方法 他们定义了一个简单的“对象”模型,并使用有界量化来类型检查函数,这些函数对具有指定“属性”集的所有对象都有意义 面向对象语言的更现实的表示将允许对象是递归地定义类型.
. 0的元素 在这种情况下,有界量化不再服务于预期的目的。很容易找到对所有具有指定方法集的对象都有意义的函数,但在Cardelli-Wegner系统中不能键入这些函数 为了在面向对象语言中为类型化多态函数提供基础,我们引入了f有界量化)

同一枚硬币的两面

在编程语言中有两种主要的抽象形式:

  • 参数化和
  • 抽象的成员。

第一种形式通常用于函数式语言,而第二种形式通常用于面向对象语言。

传统上,Java支持值的参数化,以及操作的成员抽象。 最近的带有泛型的Java 5.0也支持类型的参数化

在Scala中包含泛型的参数有两个方面:

    首先,对抽象类型进行手工编码并不是那么简单。除了在简洁性上的损失,还有一个问题就是名字的误用 模拟类型参数的抽象类型名之间的冲突

  • 其次,泛型和抽象类型通常在Scala程序中扮演不同的角色。

    • 泛型通常用于只需要类型实例化时,而
    • 抽象类型通常在需要引用摘要时使用 输入客户端代码.
      . 后者在两种情况下特别出现:
    • 人们可能希望从客户端代码中隐藏类型成员的确切定义,以获得一种sml风格的模块系统所熟知的封装。
    • 或者可能希望在子类中协变地重写类型以获得族多态性。
    • 李< / ul > < / >

    在具有有限多态性的系统中,将抽象类型重写为泛型可能需要类型边界的二次展开


    2009年10月更新

    Scala中的抽象类型成员与泛型类型参数 (Bill Venners)

    (强调我的)

    到目前为止我对抽象类型成员的观察是,在以下情况下,它们主要是比泛型类型参数更好的选择:

    • 你想让人们通过特征混合这些类型的定义
    • 你认为在定义类型成员名时显式地提到它将有助于代码的可读性

    例子:

    如果您希望将三个不同的fixture对象传递到测试中,那么您可以这样做,但是需要指定三种类型,每个参数对应一种类型。因此,如果我采用类型参数的方法,你的套件类可能最终看起来像这样:

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

    而使用类型成员方法,它看起来像这样:

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

    抽象类型成员和泛型类型参数之间的另一个微小区别是,当指定泛型类型参数时,代码的读者看不到类型参数的名称。因此,有人看到这行代码:

    // Type parameter version
    class MySuite extends FixtureSuite[StringBuilder] with StringBuilderFixture {
    // ...
    }
    

    如果不查找,他们就不知道指定为StringBuilder的类型参数的名称是什么。然而,在抽象类型成员方法中,类型参数的名称就在代码中:

    // Type member version
    class MySuite extends FixtureSuite with StringBuilderFixture {
    type FixtureParam = StringBuilder
    // ...
    }
    
    在后一种情况下,代码的读者可以看到StringBuilder是“夹具参数”类型 他们仍然需要弄清楚“fixture parameter”是什么意思,但他们至少可以在不查看文档的情况下获得类型的名称

您可以结合使用抽象类型和类型参数来建立自定义模板。

让我们假设你需要建立一个包含三个相关特征的模式:

trait AA[B,C]
trait BB[C,A]
trait CC[A,B]

在类型参数中提到的参数是AA,BB,CC本身

你可能会带来一些代码:

trait AA[B<:BB[C,AA[B,C]],C<:CC[AA[B,C],B]]
trait BB[C<:CC[A,BB[C,A]],A<:AA[BB[C,A],C]]
trait CC[A<:AA[B,CC[A,B]],B<:BB[CC[A,B],A]]

由于类型参数bond,这不能以这种简单的方式工作。你需要让它成为协变来正确继承

trait AA[+B<:BB[C,AA[B,C]],+C<:CC[AA[B,C],B]]
trait BB[+C<:CC[A,BB[C,A]],+A<:AA[BB[C,A],C]]
trait CC[+A<:AA[B,CC[A,B]],+B<:BB[CC[A,B],A]]

这个示例可以编译,但它对方差规则设置了强烈的要求,不能在某些场合使用

trait AA[+B<:BB[C,AA[B,C]],+C<:CC[AA[B,C],B]] {
def forth(x:B):C
def back(x:C):B
}
trait BB[+C<:CC[A,BB[C,A]],+A<:AA[BB[C,A],C]] {
def forth(x:C):A
def back(x:A):C
}
trait CC[+A<:AA[B,CC[A,B]],+B<:BB[CC[A,B],A]] {
def forth(x:A):B
def back(x:B):A
}

编译器将反对一堆方差检查错误

在这种情况下,你可以收集所有类型的要求在一个额外的特征和参数化其他特征

//one trait to rule them all
trait OO[O <: OO[O]] { this : O =>
type A <: AA[O]
type B <: BB[O]
type C <: CC[O]
}
trait AA[O <: OO[O]] { this : O#A =>
type A = O#A
type B = O#B
type C = O#C
def left(l:B):C
def right(r:C):B = r.left(this)
def join(l:B, r:C):A
def double(l:B, r:C):A = this.join( l.join(r,this), r.join(this,l) )
}
trait BB[O <: OO[O]] { this : O#B =>
type A = O#A
type B = O#B
type C = O#C
def left(l:C):A
def right(r:A):C = r.left(this)
def join(l:C, r:A):B
def double(l:C, r:A):B = this.join( l.join(r,this), r.join(this,l) )
}
trait CC[O <: OO[O]] { this : O#C =>
type A = O#A
type B = O#B
type C = O#C
def left(l:A):B
def right(r:B):A = r.left(this)
def join(l:A, r:B):C
def double(l:A, r:B):C = this.join( l.join(r,this), r.join(this,l) )
}

现在我们可以为所描述的模式编写具体的表示,在所有类中定义left和join方法,并免费获得right和double方法

class ReprO extends OO[ReprO] {
override type A = ReprA
override type B = ReprB
override type C = ReprC
}
case class ReprA(data : Int) extends AA[ReprO] {
override def left(l:B):C = ReprC(data - l.data)
override def join(l:B, r:C) = ReprA(l.data + r.data)
}
case class ReprB(data : Int) extends BB[ReprO] {
override def left(l:C):A = ReprA(data - l.data)
override def join(l:C, r:A):B = ReprB(l.data + r.data)
}
case class ReprC(data : Int) extends CC[ReprO] {
override def left(l:A):B = ReprB(data - l.data)
override def join(l:A, r:B):C = ReprC(l.data + r.data)
}

因此,抽象类型和类型参数都用于创建抽象。他们都有弱点和长处。抽象类型更具体,能够描述任何类型结构,但冗长且需要显式指定。类型参数可以立即创建一堆类型,但会给继承和类型边界带来额外的担忧。

它们相互提供协同作用,可以结合使用来创建复杂的抽象,这些抽象不能仅用其中一个来表达。

我认为这里没有太大的区别。类型抽象成员可以看作是公正的 存在类型,类似于其他一些函数式语言中的记录类型

例如,我们有:

class ListT {
type T
...
}

而且

class List[T] {...}

那么ListTList[_]是一样的。 类型成员的便利之处在于,我们可以使用class而无需显式的具体类型和