【问题标题】:Order of method call in Scala trait mixinScala trait mixin 中的方法调用顺序
【发布时间】:2018-04-14 12:22:58
【问题描述】:

我的程序结构如下:

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

输出是:

在增量中

翻倍

在BasicIntQueue的放

22

我对在这里订购有点困惑。我对这种场景的线性化顺序的理解是:

BasicIntQueue -> 递增 -> 加倍 -> IntQueue -> AnyRef -> 任意

那么当我调用put的时候,不应该先调用BasicIntQueue的版本吗?

【问题讨论】:

标签: scala traits


【解决方案1】:

没有。这种情况下的线性化是

{<anonymous-local>, Incrementing, Doubling, BasicIntQueue, IntQueue, AnyRef, Any}

你可以:

  1. 只需阅读规范并说服自己一定是这样的
  2. 实现规范中算法的玩具版本,查看它为各种类定义输出的内容(这有点启发性,但主要是为了好玩)

阅读规范

section 5.1.2 of the Spec 准确地告诉您如何计算线性化。您似乎忘记在L(c_n) + ... + L(c_1) 中反转索引1 ... n

如果您应用正确的公式,您将获得所涉及特征和基类的以下线性化:

     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)

您获得了上面已经显示的线性化。因此,应按以下顺序调用方法:

  • 递增
  • 加倍
  • 基本

与实际输出一致。


为了好玩重新实现线性化算法

这实际上是您可以从头开始轻松实现的规范中那些无依赖关系的 sn-ps 之一。这 替换连接的定义可以从 规范原样,它几乎是可运行的代码(除了带有箭头的有趣加号有点难以键入,而且我希望它作为列表中的中缀运算符):

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

建模类声明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 用户。

【讨论】:

  • 为什么投反对票,哪里有错误?太短,太长,“为了好玩”的部分不够有趣,或者什么......无法完全弄清楚如何建设性地反向传播这种反馈。
  • 别打扰了。不能取悦所有人,如果他们不提供建设性的反馈,你也无能为力。
【解决方案2】:

输出的排序反映了实际的线性化,即

递增 -> 加倍 -> BasicIntQueue -> IntQueue -> AnyRef -> Any

说明

类声明

class C extends S with T1 with T2

线性化为

C -> T2 -> T1 -> S

特征出现在类S 之前,因为特征可以修改S 的行为,因此必须在类层次结构中较低。同样,较晚的特征可以修改较早的特征,因此必须在类层次结构中较低。因此,线性化中类和特征的顺序与声明顺序相反。

你的例子

将此应用于您的示例,行

val incrThendoublingQueue = new BasicIntQueue with Doubling with Incrementing

大致相同

class Temp extends BasicIntQueue with Doubling with Incrementing
val incrThendoublingQueue = new Temp()

使用上面的转换,Temp 被线性化为

临时 -> 递增 -> 加倍 -> BasicIntQueue

这给出了代码输出所隐含的类层次结构。

【讨论】:

  • class Temp 扩展 BasicIntQueue 并加倍加倍.....这是关键。
猜你喜欢
  • 1970-01-01
  • 2015-04-15
  • 1970-01-01
  • 2013-11-26
  • 1970-01-01
  • 1970-01-01
  • 2012-08-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多