-->

斯卡拉:抽象类型VS仿制药(Scala: Abstract types vs generics)

2019-06-17 12:06发布

我读的Scala之旅:抽象类型 。 当是它更好地使用抽象类型?

例如,

abstract class Buffer {
  type T
  val element: T
}

而泛型,例如,

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

Answer 1:

你的观点就在这里这个问题一个很好的点:

Scala的类型系统的目的
与马丁Odersky的,第三部分的对话
由比尔和弗兰克·索莫斯(2009年5月18日)

更新(October2009):什么遵循以下实际上是由比尔这个新的文章中说明:
抽象类型成员与Scala的泛型类型参数 (见在年终总结)


(这是第一个采访的有关提取物,2009年5月,重点煤矿)

一般原则

一直存在着抽象的两个概念:

  • 参数化和
  • 抽象成员。

在Java中,你也有两个,但它取决于你在抽象什么。
在Java中,你有抽象方法,但你不能传递的方法作为参数。
你没有抽象的领域,但你可以传递一个值作为参数。
而同样你没有抽象类型成员,但你可以指定一个类型作为参数。
因此,在Java中,你也有所有这三个,但有一个什么抽象原则,你可以使用什么样的东西区别。 你可能会说,这种区别是相当武断的。

斯卡拉路

我们决定对所有三类成员的同样结构原理
所以,你可以有抽象的领域以及价值参数。
您可以通过方法(或“功能”)作为参数,或者你可以在他们的抽象。
您可以指定类型作为参数,或者你可以在他们的抽象。
而我们得到什么概念是,我们可以在其他方面的模型之一。 至少从原则上讲,我们可以表达各样的参数作为面向对象的抽象形式。 因此,在某种意义上可以说Scala是一种更正交和完整的语言。

为什么?

什么,尤其是抽象类型买你是一个很好的治疗 ,我们之前谈到这些协方差问题
一个标准的问题,这已经有很长一段时间,是动物和食物的问题。
让人不解的是,有一类Animal用的方法, eat ,吃它一些食物。
问题是,如果我们继承和动物有一个类,如牛,那么他们就只能吃草,而不是任意的食物。 一头牛不能吃的鱼,例如。
你需要的是能够说,一头牛有只吃草,而不是其他的东西吃的方法。
其实,你不能这样做,在Java中,因为事实证明,你可以构建不健全的情况下,如到我前面谈到苹果的可变分配果实的问题。

答案是, 你增加一个抽象类型为动物类
你说,我的新的动物类有一个类型的SuitableFood ,这我不知道。
所以这是一个抽象类。 你不给类型的实现。 然后,你有eat的是只吃方法SuitableFood
然后在Cow类,我会说,好吧,我有一头牛,延伸类Animal ,和Cow type SuitableFood equals Grass
所以, 抽象类型提供一个超一个类型,我不知道,然后我的东西我知道后填写在子类中的这个概念

同样的,参数化?

事实上,你可以。 你可以参数化类动物与食品的种类它吃。
在实践中,当你这样做有很多不同的东西,它导致了参数爆炸 ,通常,更重要的是, 在参数范围
在1998年ECOOP,金布鲁斯,菲尔Wadler和我有一个文件,我们发现, 如果增加的东西,你不知道电话号码,典型的程序会二次成长
因此,有很好的理由不这样做参数,但有这些抽象成员,因为他们不给你这个二次爆破。


thatismatt要求在评论:

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

  • 摘要类型中使用“具有-A”或“使用-A”的关系(如Cow eats Grass
  • 其中,作为仿制药通常都是“的”关系(例如List of Ints

我不知道该关系是使用抽象类型或仿制药之间的不同。 所不同的是:

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

To understand what Martin is speaking about when it comes to "explosion of parameters, and usually, what's more, in bounds of parameters", and its subsequent quadratically growth when abstract type are modeled using generics, you can consider the paper "Scalable Component Abstraction" written by... Martin Odersky, and Matthias Zenger for OOPSLA 2005, referenced in the publications of the project Palcom (finished in 2007).

Relevant extracts

Definition

Abstract type members provide a flexible way to abstract over concrete types of components.
Abstract types can hide information about internals of a component, similar to their use in SML signatures. In an object-oriented framework where classes can be extended by inheritance, they may also be used as a flexible means of parameterization (often called family polymorphism, see this weblog entry for instance, and the paper written by Eric Ernst).

(Note: Family polymorphism has been proposed for object-oriented languages as a solution to supporting reusable yet type-safe mutually recursive classes.
A key idea of family polymorphism is the notion of families, which are used to group mutually recursive classes)

bounded type abstraction

abstract class MaxCell extends AbsCell {
type T <: Ordered { type O = T }
def setMax(x: T) = if (get < x) set(x)
}

Here, the type declaration of T is constrained by an upper type bound which consists of a class name Ordered and a refinement { type O = T }.
The upper bound restricts the specializations of T in subclasses to those subtypes of Ordered for which the type member O of equals T.
Because of this constraint, the < method of class Ordered is guaranteed to be applicable to a receiver and an argument of type T.
The example shows that the bounded type member may itself appear as part of the bound.
(i.e. Scala supports F-bounded polymorphism)

(Note, from Peter Canning, William Cook, Walter Hill, Walter Olthoff paper:
Bounded quantification was introduced by Cardelli and Wegner as a means of typing functions that operate uniformly over all subtypes of a given type.
They defined a simple "object" model and used bounded quantification to type-check functions that make sense on all objects having a specified set of "attributes".
A more realistic presentation of object-oriented languages would allow objects that are elements of recursively-defined types.
In this context, bounded quantification no longer serves its intended purpose. It is easy to find functions that makes sense on all objects having a specified set of methods, but which cannot be typed in the Cardelli-Wegner system.
To provide a basis for typed polymorphic functions in object-oriented languages, we introduce F-bounded quantification)

Two faces of the same coins

There are two principal forms of abstraction in programming languages:

  • parameterization and
  • abstract members.

The first form is typical for functional languages, whereas the second form is typically used in object-oriented languages.

Traditionally, Java supports parameterization for values, and member abstraction for operations. The more recent Java 5.0 with generics supports parameterization also for types.

The arguments for including generics in Scala are two-fold:

  • 首先,编码成抽象类型不是简单的做手工。 除了在简洁的损失,也有仿效那个类型参数的抽象类型名之间偶然发生名称冲突的问题。

  • 其次,泛型和抽象类型通常是服务于Scala程序不同的作用。

    • 泛型通常用于当需要只需要输入的实例化 ,而
    • 当需要指从客户端代码的抽象类型 抽象类型通常使用。
      后者产生特别是在两种情况下:
    • 有人可能会想隐藏客户端代码类型成员的确切定义,获得了一种封装从SML式模块系统中已知的。
    • 或者一个可能要协变覆盖类型在子类中获得的家庭多态性。

在具有有界多态性的系统,重写抽象类型成泛型可能涉及一个类型界二次展开 。


更新2009年10月

抽象类型成员与泛型类型参数在斯卡拉 (比尔)

(重点煤矿)

据我观察,到目前为止关于抽象类型成员的是,他们主要是比一般类型参数时,一个更好的选择:

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

例:

如果你想三个不同的固定物体传递到测试,你就可以这样做,但你需要指定三种类型,每一个参数。 因此,如果我采取的类型参数的方法,你的套件类可以结束这样看:

// 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是“固定参数”型。
他们仍需要弄清楚什么是“固定参数”的意思,但他们至少可以拿到类型的名称不看的文件中。



Answer 2:

我有同样的问题,当我读到关于斯卡拉。

使用泛型的好处是,你正在创建一个家庭的类型。 没有人会需要继承Buffer -他们可以只使用Buffer[Any]Buffer[String]等。

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

你需要决定哪些是你特别需要更好。



Answer 3:

您可以使用抽象类型与类型参数一起使用,以建立自定义模板。

让我们假设你需要建立一个模式有三个连接特性:

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

这会不会是因为类型参数债券这种简单的方式工作。 你需要使它协变正确继承

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) )
}

现在,我们可以写具体表现为所描述的模式,确定左,参加所有的类中的方法,并得到正确和双重免费

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)
}

所以,无论是抽象类型和类型参数用于创建抽象。 他们都有强弱点。 摘要类型更具体,能够描述任何类型的结构,但很冗长,需要以明确的规定。 类型参数可以即时创建一堆的类型,但为您提供了关于继承和类型限制额外的担心。

他们给协同,彼此可以结合用于创建不能只用其中之一来表达复杂抽取。



文章来源: Scala: Abstract types vs generics