Scala中的线性化顺序

时间:2015-12-12 17:02:58

标签: scala traits

在处理特征时,我很难理解Scala中的线性化顺序:

class A {
  def foo() = "A"
}

trait B extends A {
  override def foo() = "B" + super.foo()
}

trait C extends B {
  override def foo() = "C" + super.foo()
}

trait D extends A {
  override def foo() = "D" + super.foo()
}

object LinearizationPlayground {
    def main(args: Array[String]) {

      var d = new A with D with C with B;
      println(d.foo) // CBDA????
  }    
}

它打印CBDA,但我无法弄清楚原因。如何确定性状的顺序?

THX

7 个答案:

答案 0 :(得分:42)

推理线性化的直观方法是参考构造顺序并可视化线性层次结构。

你可以这么想。首先构造基类;但在能够构造基类之前,必须首先构造其超类/特征(这意味着构造从层次结构的顶部开始)。对于层次结构中的每个类,混合特征从左到右构造,因为右侧的特征“稍后”添加,因此有机会“覆盖”先前的特征。然而,与类相似,为了构造特征,必须首先构建其基本特征(显而易见);并且,相当合理的是,如果已经构建了特征(层次结构中的任何位置),则不再重建特征。现在,施工顺序与线性化相反。将“基础”特征/类视为线性层次结构中较高的特征,并且层次结构中较低的特征更接近于作为线性化主题的类/对象。 线性化会影响特征中“超级”的解析方式:它将解析为最接近的基本特征(层次结构中较高)。

因此:

var d = new A with D with C with B;

A with D with C with B的线性化是

  • (层次结构顶部)A(首先构建为基类)
  • 线性化D.
    • A(以前不认为是A)
    • D(D延伸A)
  • C的线性化
    • A(以前不认为是A)
    • B(B延伸A)
    • C(C延伸B)
  • B的线性化
    • A(以前不认为是A)
    • B(以前不认为是B)

因此线性化为:A-D-B-C。 您可以将其视为线性层次结构,其中A是根(最高)并且首先构造,C是叶(最低)并且最后构造。由于C是最后构造的,这意味着可以覆盖“之前”的成员。

根据这些直观的规则,d.foo调用C.foo,它会返回一个“C”,后跟super.foo(),该{C}在B上解析(B上的特征{1}},即更高/之前,在线性化中),返回“B”后跟super.foo(),在D上解析,返回“D”后跟{{1}在super.foo()上解析,最终返回“A”。所以你有“CBDA”。

另一个例子,我准备了以下一个:

A

答案 1 :(得分:12)

Scala的特征堆栈,所以你可以通过一次添加一个来查看它们:

  1. [include] ../node_modules/ramda =>开始new A
  2. Stack foo = "A" => with D
  3. 堆叠foo = "DA" =>的堆栈with C with B
  4. 堆栈foo = "CBDA"不执行任何操作,因为with B已经堆叠在B =>中C
  5. 这里是关于Scala如何解决钻石继承问题的blog post

答案 2 :(得分:12)

接受的答案很精彩,但是,为了简化起见,我想以不同的方式尽力描述它。希望可以帮助一些人。

遇到线性化问题时, 第一步 是绘制类和特征的层次结构树。对于此特定示例,层次结构树将是这样的:

enter image description here

第二步 是写下干扰目标问题的特征和类的所有线性化。在最后一步之前,你将需要它们。为此,您只需要编写到达根目录的路径。特征的线性化如下:

L(A) = A
L(C) = C -> B -> A
L(B) = B -> A
L(D) = D -> A

第三步是编写问题的线性化。在这个具体问题中,我们计划解决

的线性化问题
  

var d = new A with D with C with B;

重要提示是,有一条规则可以通过首先使用右前优先深度优先搜索来解析方法调用。换句话说,您应该从最右侧开始编写线性化。它如下: L(B)>> L(C)>> L(d)>> L(A)

第四步 是最简单的一步。只需将每个线性化从第二步替换为第三步。替换后,你会有这样的事情:

B -> A -> C -> B -> A -> D -> A -> A

最后但并非最不重要的 ,您现在应该从左到右删除所有重复的类。应删除粗体字符:      B - > A - > C - > B - > A - > D - > A - >甲

你知道,你有结果: C - > B - > D - >的 A 因此答案是CBDA。

我知道这不是个别深刻的概念描述,但可以作为我猜想的概念性描述的补充。

这一部分依靠公式解释:

 Lin(new A with D with C with B) = {A, Lin(B), Lin(C), Lin(D)}
 Lin(new A with D with C with B) = {A, Lin(B), Lin(C), {D, Lin(A)}}
 Lin(new A with D with C with B) = {A, Lin(B), Lin(C), {D, A}}
 Lin(new A with D with C with B) = {A, Lin(B), {C, Lin(B)}, {D, A}}
 Lin(new A with D with C with B) = {A, Lin(B), {C, {B, Lin(A)}}, {D, A}}
 Lin(new A with D with C with B) = {A, Lin(B), {C, {B, A}}, {D, A}}
 Lin(new A with D with C with B) = {A, {B, A}, {C, {B, A}}, {D, A}}
 Lin(new A with D with C with B) = {C,B,D,A}

答案 3 :(得分:6)

scala解析超级调用的过程称为线性化 在您的示例中,您将对象创建为

var d = new A with D with C with B;

因此,指定的scala引用文档Here对super的调用将被解析为

l(A) = A >> l(B) >> l(c) >> l(D)

l(A) = A >> B >> l(A) >> l(C) >> l(D)

l(A) = A >> B >> A >> C >> l(B) >> l(D)

l(A) = A >> B >> A >> C >> B >> l(A) >> l(D)

l(A) = A >> B >> A >> C >> B >> A >> l(D)

l(A) = A >> B >> A >> C >> B >> A >> D >> l(A)

l(A) = A >> B >> A >> C >> B >> A >> D >> A

现在从左边开始删除重复的构造,其中右边将赢得一个

e.g。删除A我们得到

l(A) = B >> C >> B >> D >> A

删除B,我们得到

l(A) = C >> B >> D >> A

这里我们没有任何重复的条目 现在开始从C

开始呼叫

C B D A

课程C中的

super.foo会在B中调用foo,在D中调用B调用foo,依此类推。

<强> P.S。这里l(A)是A

的线性化

答案 4 :(得分:1)

解释,编译器如何看到扩展特征<blockquote> <pre> For now I know that all I lived and was Moved towards this moment of my heart's rebirth; I look back on the meaning of myself, A soul made ready on earth's soil for thee. </pre> </blockquote> <blockquote> <pre> For now I know that all I lived and was Moved towards this moment of my heart's rebirth; I look back on the meaning of myself, A soul made ready on earth's soil for thee. </pre> </blockquote>

的类Combined
A with D with C with B

简化示例

您可以从左到右阅读。这是一个小例子。这三个特征将在初始化时打印出来,即扩展名称:

class Combined extends A with D with C with B {
  final <superaccessor> <artifact> def super$foo(): String = B$class.foo(Combined.this);
  override def foo(): String = C$class.foo(Combined.this);
  final <superaccessor> <artifact> def super$foo(): String = D$class.foo(Combined.this);
  final <superaccessor> <artifact> def super$foo(): String =  Combined.super.foo();
  def <init>(): Combined = {
    Combined.super.<init>();
    D$class./*D$class*/$init$(Combined.this);
    B$class./*B$class*/$init$(Combined.this);
    C$class./*C$class*/$init$(Combined.this);
    ()
  }
};

所以这是基本的线性化顺序。所以最后一个会覆盖前一个。

你的问题有点复杂。由于你的特征已经扩展了其他特征,这些特征本身会覆盖以前特征的某些值。 但初始化顺序为scala> trait A {println("A")} scala> trait B {println("B")} scala> trait C {println("C")} scala> new A with B with C A B C res0: A with B with C = $anon$1@5e025e70 scala> new A with C with B A C B res1: A with C with B = $anon$1@2ed94a8b left to right

你必须记住,特征本身将首先被初始化。

答案 5 :(得分:1)

除了其他版本之外,您还可以在下面的代码段结果中找到分步说明

hljs.initHighlightingOnLoad();
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.0.0/highlight.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.0.0/styles/zenburn.min.css" rel="stylesheet" />
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" />
<table class="table">
  <tr>
    <th>Expression</th>
    <th>type</th>
    <th><code>foo()</code> result</th>
  </tr>

  <tr>
    <td><pre><code class="scala"> new A </code></pre>
    </td>
    <td><pre><code class="scala"> A </code></pre>
    </td>
    <td><pre><code class="scala">"A"</code></pre>
    </td>
  </tr>

  <tr>
    <td><pre><code class="scala"> new A with D </code></pre>
    </td>
    <td><pre><code class="scala"> D </code></pre>
    </td>
    <td><pre><code class="scala">"DA"</code></pre>
    </td>
  </tr>
  
    <tr>
    <td><pre><code class="scala"> new A with D with C </code></pre>
    </td>
    <td><pre><code class="scala"> D with C </code></pre>
    </td>
    <td><pre><code class="scala">"CBDA"</code></pre>
    </td>
  </tr>
  
   <tr>
    <td><pre><code class="scala"> new A with D with C with B </code></pre>
    </td>
    <td><pre><code class="scala"> D with C </code></pre>
    </td>
    <td><pre><code class="scala">"CBDA"</code></pre>
    </td>
  </tr>
</table>

答案 6 :(得分:0)

实际上我看到你刚刚颠倒了构造函数的线性化,我认为这很简单,所以首先让我们理解构造函数的线性化

第一个例子

object Linearization3 {
  def main(args: Array[String]) {
    var x = new X
    println()
    println(x.foo)
  }
}

class A {
  print("A")

  def foo() = "A"
}

trait B extends A {
  print("B")

  override def foo() =   super.foo() + "B" // Hence I flipped yours to give exact output as constructor
}

trait C extends B {
  print("C")

  override def foo() =  super.foo() + "C"
}

trait D extends A {
  print("D")

  override def foo() =  super.foo() + "D"
}

class X extends A with D with C with B

哪个输出:

ADBC
ADBC

所以要计算输出我只是从左到右依次取类/特征然后递归写输出(没有重复)这里是如何:

  1. 我们的班级签名是:class X extends A with D with C with B
  2. 所以第一个是A,因为A没有父母(deadend)只打印它的构造函数
  3. 现在D,它扩展了A,因为我们已经打印了A,然后让我们打印D
  4. 现在C,它扩展了B,扩展了A,所以我们跳过A因为它已经打印过了,然后打印B,然后打印C(它就像递归函数)
  5. 现在B,扩展A,我们跳过A,我们也跳过B(没有打印)
  6. 你得到了ADBC!
  7. 反转示例(您的示例)

    object Linearization3 {
      def main(args: Array[String]) {
        var x = new X
        println()
        println(x.foo)
      }
    }
    
    class A {
      print("A")
    
      def foo() = "A"
    }
    
    trait B extends A {
      print("B")
    
      override def foo() = "B" + super.foo()
    }
    
    trait C extends B {
      print("C")
    
      override def foo() = "C" + super.foo()
    }
    
    trait D extends A {
      print("D")
    
      override def foo() = "D" + super.foo()
    }
    
    class X extends A with D with C with B
    

    输出是:

    ADBC
    CBDA
    

    我希望对于像我这样的初学者来说这很简单