Scala trait mixin中方法调用的顺序
Order of method call in Scala trait mixin
我的程序结构如下:
abstract class IntQueue {
def get(): Int
def put(x: Int)
}
trait Doubling extends IntQueue{
abstract override def put(x: Int) {
println("In Doubling's put")
super.put(2*x)
}
}
trait Incrementing extends IntQueue {
abstract override def put(x: Int) {
println("In Incrementing's put")
super.put(x + 1)
}
}
class BasicIntQueue extends IntQueue {
private val buf = new ArrayBuffer[Int]
def get() = buf.remove(0)
def put(x: Int) {
println("In BasicIntQueue's put")
buf += x
}
}
当我这样做时:
val incrThendoublingQueue = new BasicIntQueue with Doubling with
Incrementing
incrThendoublingQueue.put(10)
println(incrThendoublingQueue.get())
输出为:
In Incrementing's put
In Doubling's put
In BasicIntQueue's put
22
我对在这里订购有点困惑。我对这种情况的线性化顺序的理解是:
BasicIntQueue -> Incrementing -> Doubling -> IntQueue -> AnyRef -> Any
那我调用put的时候不是应该先调用BasicIntQueue的版本吗?
没有。这种情况下的线性化是
{<anonymous-local>, Incrementing, Doubling, BasicIntQueue, IntQueue, AnyRef, Any}
您可以:
- 只需阅读规范并说服自己一定是这样
- 从规范中实现算法的玩具版本,查看它针对各种 class 定义输出的内容(这有点启发性,但主要是为了好玩)
阅读规范
section 5.1.2 of the Spec
准确地告诉您线性化是如何计算的。您似乎忘记了在 L(c_n) + ... + L(c_1)
.
中反转索引 1 ... n
如果您应用正确的公式,您将得到以下涉及的特征和基础 classes 的线性化:
IntQueue : {IntQueue, AnyRef, Any}
Doubling : {Doubling, IntQueue, AnyRef, Any}
Incrementing : {Incrementing, IntQueue, AnyRef, Any}
BasicIntQueue : {BasicIntQueue, IntQueue, AnyRef, Any}
如果您最终组合这些线性化来计算实例化为 incrThendoublingQueue
:
的匿名本地 class 的线性化
<anonymous-local-class>, L(Incrementing) + L(Doubling) + L(BasicInt)
你得到上面已经显示的线性化。因此,应按以下顺序调用方法:
- 递增
- 加倍
- 基本
与实际输出一致
为了好玩重新实现线性化算法
这实际上是您可以从头开始轻松实施的规范的无依赖性片段之一。这
可以从中复制带替换连接的定义
规范原样,它几乎是可运行的代码(除了带有箭头的有趣加号有点难以输入,而且我希望它作为列表中的中缀运算符):
implicit class ConcatenationWithReplacementOps[A](list: List[A]) {
def +^->(other: List[A]): List[A] = list match {
case Nil => other
case h :: t =>
if (other contains h) (t +^-> other)
else h :: (t +^-> other)
}
}
建模 class 声明 C extends C1 with ... with Cn
也是
真的很简单:
case class ClassDecl(c: String, extendsTemplate: List[ClassDecl]) {
def linearization: List[String] = c :: (
extendsTemplate
.reverse
.map(_.linearization)
.foldLeft(List.empty[String])(_ +^-> _)
)
}
线性化的公式在这里作为一种方法实现。注意 reverse
.
规范中给出的示例:
val any = ClassDecl("Any", Nil)
val anyRef = ClassDecl("AnyRef", List(any))
val absIterator = ClassDecl("AbsIterator", List(anyRef))
val richIterator = ClassDecl("RichIterator", List(absIterator))
val stringIterator = ClassDecl("StringIterator", List(absIterator))
val iter = ClassDecl("Iter", List(stringIterator, richIterator))
println(iter.linearization.mkString("{", ", ", "}"))
完全按照规范生成输出:
{Iter, RichIterator, StringIterator, AbsIterator, AnyRef, Any}
现在,这是您的示例的模型:
val intQueue = ClassDecl("IntQueue", List(anyRef))
val doubling = ClassDecl("Doubling", List(intQueue))
val incrementing = ClassDecl("Incrementing", List(intQueue))
val basicQueue = ClassDecl("BasicIntQueue", List(intQueue))
val incrThendoublingQueue = ClassDecl(
"<anonymous-local>",
List(basicQueue, doubling, incrementing)
)
println(incrThendoublingQueue.linearization.mkString("{", ", ", "}"))
它产生我上面已经显示的线性化顺序:
{<anonymous-local>, Incrementing, Doubling, BasicIntQueue, IntQueue, AnyRef, Any}
一切似乎都按预期工作,没有理由写信给 Scala-Users。
输出的排序反映了实际的线性化,即
Incrementing -> Doubling -> BasicIntQueue -> IntQueue -> AnyRef -> Any
说明
一个class声明
class C extends S with T1 with T2
线性化为
C -> T2 -> T1 -> S
特征出现在 class S
之前,因为特征可以修改 S
的行为,因此在 class 层次结构中必须较低。同样,较晚的特征可以修改较早的特征,因此在 class 层次结构中必须较低。因此,线性化中 classes 和 traits 的顺序与声明顺序相反。
你的例子
将此应用于您的示例,行
val incrThendoublingQueue = new BasicIntQueue with Doubling with Incrementing
与
大致相同
class Temp extends BasicIntQueue with Doubling with Incrementing
val incrThendoublingQueue = new Temp()
使用上面的转换,Temp
被线性化为
Temp -> Incrementing -> Doubling -> BasicIntQueue
这给出了代码输出隐含的 class 层次结构。
我的程序结构如下:
abstract class IntQueue {
def get(): Int
def put(x: Int)
}
trait Doubling extends IntQueue{
abstract override def put(x: Int) {
println("In Doubling's put")
super.put(2*x)
}
}
trait Incrementing extends IntQueue {
abstract override def put(x: Int) {
println("In Incrementing's put")
super.put(x + 1)
}
}
class BasicIntQueue extends IntQueue {
private val buf = new ArrayBuffer[Int]
def get() = buf.remove(0)
def put(x: Int) {
println("In BasicIntQueue's put")
buf += x
}
}
当我这样做时:
val incrThendoublingQueue = new BasicIntQueue with Doubling with
Incrementing
incrThendoublingQueue.put(10)
println(incrThendoublingQueue.get())
输出为:
In Incrementing's put
In Doubling's put
In BasicIntQueue's put
22
我对在这里订购有点困惑。我对这种情况的线性化顺序的理解是:
BasicIntQueue -> Incrementing -> Doubling -> IntQueue -> AnyRef -> Any
那我调用put的时候不是应该先调用BasicIntQueue的版本吗?
没有。这种情况下的线性化是
{<anonymous-local>, Incrementing, Doubling, BasicIntQueue, IntQueue, AnyRef, Any}
您可以:
- 只需阅读规范并说服自己一定是这样
- 从规范中实现算法的玩具版本,查看它针对各种 class 定义输出的内容(这有点启发性,但主要是为了好玩)
阅读规范
section 5.1.2 of the Spec
准确地告诉您线性化是如何计算的。您似乎忘记了在 L(c_n) + ... + L(c_1)
.
1 ... n
如果您应用正确的公式,您将得到以下涉及的特征和基础 classes 的线性化:
IntQueue : {IntQueue, AnyRef, Any}
Doubling : {Doubling, IntQueue, AnyRef, Any}
Incrementing : {Incrementing, IntQueue, AnyRef, Any}
BasicIntQueue : {BasicIntQueue, IntQueue, AnyRef, Any}
如果您最终组合这些线性化来计算实例化为 incrThendoublingQueue
:
<anonymous-local-class>, L(Incrementing) + L(Doubling) + L(BasicInt)
你得到上面已经显示的线性化。因此,应按以下顺序调用方法:
- 递增
- 加倍
- 基本
与实际输出一致
为了好玩重新实现线性化算法
这实际上是您可以从头开始轻松实施的规范的无依赖性片段之一。这 可以从中复制带替换连接的定义 规范原样,它几乎是可运行的代码(除了带有箭头的有趣加号有点难以输入,而且我希望它作为列表中的中缀运算符):
implicit class ConcatenationWithReplacementOps[A](list: List[A]) {
def +^->(other: List[A]): List[A] = list match {
case Nil => other
case h :: t =>
if (other contains h) (t +^-> other)
else h :: (t +^-> other)
}
}
建模 class 声明 C extends C1 with ... with Cn
也是
真的很简单:
case class ClassDecl(c: String, extendsTemplate: List[ClassDecl]) {
def linearization: List[String] = c :: (
extendsTemplate
.reverse
.map(_.linearization)
.foldLeft(List.empty[String])(_ +^-> _)
)
}
线性化的公式在这里作为一种方法实现。注意 reverse
.
规范中给出的示例:
val any = ClassDecl("Any", Nil)
val anyRef = ClassDecl("AnyRef", List(any))
val absIterator = ClassDecl("AbsIterator", List(anyRef))
val richIterator = ClassDecl("RichIterator", List(absIterator))
val stringIterator = ClassDecl("StringIterator", List(absIterator))
val iter = ClassDecl("Iter", List(stringIterator, richIterator))
println(iter.linearization.mkString("{", ", ", "}"))
完全按照规范生成输出:
{Iter, RichIterator, StringIterator, AbsIterator, AnyRef, Any}
现在,这是您的示例的模型:
val intQueue = ClassDecl("IntQueue", List(anyRef))
val doubling = ClassDecl("Doubling", List(intQueue))
val incrementing = ClassDecl("Incrementing", List(intQueue))
val basicQueue = ClassDecl("BasicIntQueue", List(intQueue))
val incrThendoublingQueue = ClassDecl(
"<anonymous-local>",
List(basicQueue, doubling, incrementing)
)
println(incrThendoublingQueue.linearization.mkString("{", ", ", "}"))
它产生我上面已经显示的线性化顺序:
{<anonymous-local>, Incrementing, Doubling, BasicIntQueue, IntQueue, AnyRef, Any}
一切似乎都按预期工作,没有理由写信给 Scala-Users。
输出的排序反映了实际的线性化,即
Incrementing -> Doubling -> BasicIntQueue -> IntQueue -> AnyRef -> Any
说明
一个class声明
class C extends S with T1 with T2
线性化为
C -> T2 -> T1 -> S
特征出现在 class S
之前,因为特征可以修改 S
的行为,因此在 class 层次结构中必须较低。同样,较晚的特征可以修改较早的特征,因此在 class 层次结构中必须较低。因此,线性化中 classes 和 traits 的顺序与声明顺序相反。
你的例子
将此应用于您的示例,行
val incrThendoublingQueue = new BasicIntQueue with Doubling with Incrementing
与
大致相同class Temp extends BasicIntQueue with Doubling with Incrementing
val incrThendoublingQueue = new Temp()
使用上面的转换,Temp
被线性化为
Temp -> Incrementing -> Doubling -> BasicIntQueue
这给出了代码输出隐含的 class 层次结构。