小编典典

Scala:抽象类型与泛型

all

我正在阅读 Scala 之旅:抽象类型 。什么时候使用抽象类型更好?

例如,

abstract class Buffer {
  type T
  val element: T
}

而不是泛型,例如,

abstract class Buffer[T] {
  val element: T
}

阅读 93

收藏
2022-05-16

共1个答案

小编典典

您在这里对这个问题有很好的看法:

Scala
类型系统的目的

与 Martin Odersky 的对话,第三部分
Bill Venners 和 Frank Sommers(2009 年 5 月 18 日)

更新(2009 年 10 月):以下内容实际上已在 Bill Venners 的这篇新文章中进行了说明:
Scala
中的抽象类型成员与通用类型参数
(请参阅末尾的摘要)


(这是2009年5月第一次采访的相关摘录,强调我的)

一般原则

一直有两个抽象概念:

  • 参数化和
  • 抽象成员。

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

斯卡拉之道

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

为什么?

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

答案是 您将抽象类型添加到 Animal 类 中。
你说,我的新 Animal 类有一个类型SuitableFood,我不知道。
所以它是一个抽象类型。您没有给出该类型的实现。那你就有了eat只吃的方法SuitableFood
然后在Cow课堂上我会说,好的,我有一头牛,它扩展了 classAnimal和 for Cow type SuitableFood equals Grass
所以 抽象类型在我不知道的超类中提供了这种类型的概念,然后我用我知道的东西在子类中填充它

与参数化相同吗?

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


thatismatt在评论中问道:

你认为以下是一个公平的总结:

  • 抽象类型用于“has-a”或“uses-a”关系(例如 a Cow eats Grass
  • 其中泛型通常是“关系”(例如List of Ints

我不确定使用抽象类型或泛型之间的关系是否不同。不同的是:

  • 如何使用它们,以及
  • 如何管理参数边界。

要理解 Martin 在谈到“参数的爆炸,通常更重要的是,在 参数的范围内
”以及在使用泛型对抽象类型建模时随后的二次增长时所说的内容,您可以考虑论文“ Scalable Component
Abstraction
” “
由… Martin Odersky 和 ​​Matthias Zenger 为 OOPSLA 2005 编写,在Palcom
项目的出版物中
引用(2007 年完成)。

相关摘录

定义

抽象类型成员 提供了一种灵活的方式来抽象具体类型的组件。
抽象类型可以隐藏有关组件内部的信息,类似于它们在
SML

签名中的使用。在可以通过继承扩展类的面向对象框架中,它们也可以用作灵活的参数化方式(通常称为族多态,例如,请参见此博客条目,以及Eric
Ernst
撰写的论文)。

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

有界类型抽象

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 的子类型,其类型成员Oequals T.
由于这个约束,<Ordered 类的方法保证适用于接收者和类型 T 的参数。
该示例显示有界类型成员本身可能作为边界的一部分出现。
(即 Scala 支持F-bounded
polymorphism

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

同一枚硬币的两个面

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

  • 参数化和
  • 抽象成员。

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

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

在 Scala 中包含泛型的论点有两个方面:

  • 首先,手工编码成抽象类型并不是那么简单。除了在简洁性上的损失之外,还存在模拟类型参数的抽象类型名称之间意外名称冲突的问题。

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

    • **当只需要类型实例化 时,通常会使用 泛型 ,而
    • **当需要从客户端代码中引用 抽象类型 时,通常会使用抽象类型。
      后者尤其出现在两种情况下:

    • 有人可能想从客户端代码中隐藏类型成员的确切定义,以获得一种从 SML 样式模块系统中已知的封装。

    • 或者可能希望在子类中以协变方式覆盖类型以获得家族多态性。

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


2009 年 10 月更新

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

(强调我的)

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

  • 你想让人们 通过 traits 混合这些类型的定义
  • 您认为在 定义类型成员名称时明确提及将有助于代码的可读性

例子:

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

// 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是“夹具参数”类型。
他们仍然需要弄清楚“夹具参数”的含义,但他们至少可以在不查看文档的情况下获得类型的名称。

2022-05-16