Scala:抽象类型与泛型

时间:2009-07-20 16:30:59

标签: generics scala abstract-type

我正在阅读 A Tour of Scala: Abstract Types 。什么时候使用抽象类型更好?

例如,

abstract class Buffer {
  type T
  val element: T
}

而不是泛型,例如,

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

4 个答案:

答案 0 :(得分:239)

答案 1 :(得分:37)

当我读到Scala时,我有同样的问题。

使用泛型的优点是您正在创建一系列类型。没有人需要继承Buffer - 他们只能使用Buffer[Any]Buffer[String]等。

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

您需要决定哪个更适合您的特定需求。

答案 2 :(得分:18)

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

假设您需要建立一个具有三个连接特征的模式:

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

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

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

因此,抽象类型和类型参数都用于创建抽象。他们都有弱点和强点。抽象类型更具体,能够描述任何类型结构,但是冗长且需要明确指定。类型参数可以立即创建一堆类型,但是会让您更加担心继承和类型边界。

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

答案 3 :(得分:0)

我认为这里没有太大区别。类型抽象成员可以看作只是 存在类型,类似于某些其他功能语言中的记录类型。

例如,我们有:

fn my_function() -> ! {
    //function body
}

class ListT {
  type T
  ...
}

然后class List[T] {...} ListT相同。 类型成员的便利之处在于我们可以使用没有显式具体类型的类,并且 避免输入太多类型的参数。