我正在阅读 A Tour of Scala: Abstract Types 。什么时候使用抽象类型更好?
例如,
abstract class Buffer {
type T
val element: T
}
而不是泛型,例如,
abstract class Buffer[T] {
val element: T
}
答案 0 :(得分:239)
答案 1 :(得分:37)
当我读到Scala时,我有同样的问题。
使用泛型的优点是您正在创建一系列类型。没有人需要继承Buffer
- 他们只能使用Buffer[Any]
,Buffer[String]
等。
如果使用抽象类型,那么人们将被迫创建子类。人们需要AnyBuffer
,StringBuffer
等类。
您需要决定哪个更适合您的特定需求。
答案 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
相同。
类型成员的便利之处在于我们可以使用没有显式具体类型的类,并且
避免输入太多类型的参数。