Scala trait mixin中方法调用的顺序

时间:2018-04-14 12:22:59

标签: scala traits

我的程序结构如下:

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

输出是:

  

在增量投入中

     

在Doubling's put中

     

在BasicIntQueue的put

中      

22

我对这里的订购感到有点困惑。我对这种情况的线性化顺序的理解是:

  

BasicIntQueue - >增量 - >加倍 - > IntQueue - > AnyRef - >任何

所以当我调用put时,不应该首先调用BasicIntQueue的版本吗?

2 个答案:

答案 0 :(得分:3)

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

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

你可以:

  1. 只需阅读规范并说服自己必须如此
  2. 从规范中实现一个玩具版本的算法,看看它为各种类定义输出了什么(它有点启发,但主要是为了好玩)
  3. 阅读规范

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

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

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

    对类声明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。

答案 1 :(得分:1)

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

  

增量 - &gt;加倍 - &gt; BasicIntQueue - &gt; IntQueue - &gt; AnyRef - &gt;任何

解释

班级宣言

class C extends S with T1 with T2

线性化为

  

C - &gt; T2 - &gt; T1 - &gt;小号

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

你的例子

将此应用于您的示例,行

val incrThendoublingQueue = new BasicIntQueue with Doubling with Incrementing

大致相同
class Temp extends BasicIntQueue with Doubling with Incrementing
val incrThendoublingQueue = new Temp()

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

  

温度 - &gt;增量 - &gt;加倍 - &gt; BasicIntQueue

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