Scala 中的线性化顺序
Linearization order in Scala
在使用特征时,我很难理解 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
但我不明白为什么。性状的顺序是如何确定的?
感谢
Scala 的 traits 堆栈,因此您可以一次添加一个来查看它们:
- 从
new A
开始 => foo = "A"
- 堆栈
with D
=> foo = "DA"
- 堆栈
with C
堆栈 with B
=> foo = "CBDA"
- Stack
with B
什么都不做,因为 B
已经堆叠在 C
=> foo = "CBDA"
这里是blog post关于Scala如何解决菱形继承问题的
解释,编译器如何看待扩展特征 A with D with C with B
的 class Combined
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@5e025e70
scala> new A with C with B
A
C
B
res1: A with C with B = $anon@2ed94a8b
所以这是基本的线性化顺序。所以最后一个会覆盖上一个
你的问题有点复杂。由于您的特征已经扩展了其他特征,这些特征本身会覆盖先前特征的某些值。
但是初始化顺序left to right
还是right will override left
.
你必须记住,特征本身将首先被初始化。
推理线性化的一种直观方法是参考构造顺序并可视化线性层次结构。
你可以这么想。首先构建基础class;但是在能够构建基础 class 之前,必须先构建它的 superclasses/traits (这意味着构建从层次结构的顶部开始)。对于层次结构中的每个 class,混合特征是从左到右构造的,因为右侧的特征是“稍后”添加的,因此有机会“覆盖”先前的特征。但是,与 classes 类似,为了构造一个特征,必须首先构造它的基本特征(显而易见);并且,相当合理的是,如果已经构造了一个特征(在层次结构中的任何地方),则不会再次重建它。现在,构造顺序与线性化相反。将“基础”traits/classes 视为线性层次结构中的较高层次,将层次结构中较低的特征视为更接近线性化主题的 class/object。
线性化会影响“超级”在特征中的解析方式:它将解析为最接近的基本特征(在层次结构中更高)。
因此:
var d = new A with D with C with B;
A with D with C with B
的线性化是
- (层次结构的顶部)A(首先构造为基础 class)
- 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
,其中 returns 一个“C”,后跟 super.foo()
,在 B
上解析( B
左侧的特征,即 higher/before,在线性化中),其中 returns 一个“B”,后跟 super.foo()
,在 D
上解析,其中 returns 一个“D”,然后是 super.foo()
,在 A
上解析,最后是 returns“A”。所以你有“CBDA”。
再举个例子,我准备了下面的:
class X { print("X") }
class A extends X { print("A") }
trait H { print("H") }
trait S extends H { print("S") }
trait R { print("R") }
trait T extends R with H { print("T") }
class B extends A with T with S { print("B") }
new B // X A R H T S B (the prints follow the construction order)
// Linearization is the reverse of the construction order.
// Note: the rightmost "H" wins (traits are not re-constructed)
// lin(B) = B >> lin(S) >> lin(T) >> lin(A)
// = B >> (S >> H) >> (T >> H >> R) >> (A >> X)
// = B >> S >> T >> H >> R >> A >> X
除了其他答案之外,您还可以在下面的摘要结果中找到分步说明
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>
scala解析super调用的过程叫做线性化
在您的示例中,您将对象创建为
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
现在从左边开始并删除重复的结构,其中右边将获胜
例如删除 A 我们得到
l(A) = B >> C >> B >> D >> A
删除 B 我们得到
l(A) = C >> B >> D >> A
这里没有重复的条目
现在开始从 C
调用
C B D A
super.foo in class C
将在 B
中调用 foo 并且 B 中的 foo 在 D
中调用 foo 等等.
P.S。这里 l(A) 是 A
的线性化
其实我看你只是把构造函数线性化反了过来,我觉得挺简单的,所以先了解一下构造函数线性化
第一个例子
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
所以为了计算输出我只是从左到右一个一个地取 class/traits 然后递归地写输出(没有重复)这里是如何:
- 我们的 class 签名是:
class X extends A with D with C with B
- 所以第一个是 A,因为 A 没有父节点(死端)只打印它的构造函数
- 现在是D,它扩展了A,既然我们已经打印了A,那么让我们打印D
- 现在 C 扩展 B,扩展 A,所以我们跳过 A,因为它已经被打印,然后我们打印 B,然后打印 C(这就像一个递归函数)
- 现在 B,它扩展了 A,我们跳过 A,我们也跳过 B(没有打印)
- 你得到了 ADBC!
反向示例(您的示例)
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
我希望这对像我这样的初学者足够简单
接受的答案很好,但是,为了简单起见,我想尽量用不同的方式描述它。希望能帮到一些人。
遇到线性化问题,第一步是画出类和traits的hierarchy tree .对于这个特定示例,层次结构树将是这样的:
第二步是写下所有干扰目标问题的traits和类的线性化。在最后一步之前,您将需要它们。为此,您只需编写到达根目录的路径。特征的线性化如下:
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;
重要的注意事项是,它通过首先使用右优先、深度优先搜索来解析方法调用。换句话说,你应该从最右边开始写线性化。它是这样的:
大号(B)>>大号(C)>>大号(D)>>大号(A)
第四步是最简单的一步。只需将每个线性化从第二步替换到第三步即可。替换后,你会得到这样的东西:
B -> A -> C -> B -> A -> D -> A -> A
最后但并非最不重要的,您现在应该从左到右删除所有重复的 类。应删除粗体字符:
B -> A -> C -> B -> A -> D -> A -> 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}
在使用特征时,我很难理解 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
但我不明白为什么。性状的顺序是如何确定的?
感谢
Scala 的 traits 堆栈,因此您可以一次添加一个来查看它们:
- 从
new A
开始 =>foo = "A"
- 堆栈
with D
=>foo = "DA"
- 堆栈
with C
堆栈with B
=>foo = "CBDA"
- Stack
with B
什么都不做,因为B
已经堆叠在C
=>foo = "CBDA"
这里是blog post关于Scala如何解决菱形继承问题的
解释,编译器如何看待扩展特征 A with D with C with B
的 class Combined
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@5e025e70
scala> new A with C with B
A
C
B
res1: A with C with B = $anon@2ed94a8b
所以这是基本的线性化顺序。所以最后一个会覆盖上一个
你的问题有点复杂。由于您的特征已经扩展了其他特征,这些特征本身会覆盖先前特征的某些值。
但是初始化顺序left to right
还是right will override left
.
你必须记住,特征本身将首先被初始化。
推理线性化的一种直观方法是参考构造顺序并可视化线性层次结构。
你可以这么想。首先构建基础class;但是在能够构建基础 class 之前,必须先构建它的 superclasses/traits (这意味着构建从层次结构的顶部开始)。对于层次结构中的每个 class,混合特征是从左到右构造的,因为右侧的特征是“稍后”添加的,因此有机会“覆盖”先前的特征。但是,与 classes 类似,为了构造一个特征,必须首先构造它的基本特征(显而易见);并且,相当合理的是,如果已经构造了一个特征(在层次结构中的任何地方),则不会再次重建它。现在,构造顺序与线性化相反。将“基础”traits/classes 视为线性层次结构中的较高层次,将层次结构中较低的特征视为更接近线性化主题的 class/object。 线性化会影响“超级”在特征中的解析方式:它将解析为最接近的基本特征(在层次结构中更高)。
因此:
var d = new A with D with C with B;
A with D with C with B
的线性化是
- (层次结构的顶部)A(首先构造为基础 class)
- 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
,其中 returns 一个“C”,后跟 super.foo()
,在 B
上解析( B
左侧的特征,即 higher/before,在线性化中),其中 returns 一个“B”,后跟 super.foo()
,在 D
上解析,其中 returns 一个“D”,然后是 super.foo()
,在 A
上解析,最后是 returns“A”。所以你有“CBDA”。
再举个例子,我准备了下面的:
class X { print("X") }
class A extends X { print("A") }
trait H { print("H") }
trait S extends H { print("S") }
trait R { print("R") }
trait T extends R with H { print("T") }
class B extends A with T with S { print("B") }
new B // X A R H T S B (the prints follow the construction order)
// Linearization is the reverse of the construction order.
// Note: the rightmost "H" wins (traits are not re-constructed)
// lin(B) = B >> lin(S) >> lin(T) >> lin(A)
// = B >> (S >> H) >> (T >> H >> R) >> (A >> X)
// = B >> S >> T >> H >> R >> A >> X
除了其他答案之外,您还可以在下面的摘要结果中找到分步说明
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>
scala解析super调用的过程叫做线性化 在您的示例中,您将对象创建为
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
现在从左边开始并删除重复的结构,其中右边将获胜
例如删除 A 我们得到
l(A) = B >> C >> B >> D >> A
删除 B 我们得到
l(A) = C >> B >> D >> A
这里没有重复的条目 现在开始从 C
调用C B D A
super.foo in class C
将在 B
中调用 foo 并且 B 中的 foo 在 D
中调用 foo 等等.
P.S。这里 l(A) 是 A
的线性化其实我看你只是把构造函数线性化反了过来,我觉得挺简单的,所以先了解一下构造函数线性化
第一个例子
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
所以为了计算输出我只是从左到右一个一个地取 class/traits 然后递归地写输出(没有重复)这里是如何:
- 我们的 class 签名是:
class X extends A with D with C with B
- 所以第一个是 A,因为 A 没有父节点(死端)只打印它的构造函数
- 现在是D,它扩展了A,既然我们已经打印了A,那么让我们打印D
- 现在 C 扩展 B,扩展 A,所以我们跳过 A,因为它已经被打印,然后我们打印 B,然后打印 C(这就像一个递归函数)
- 现在 B,它扩展了 A,我们跳过 A,我们也跳过 B(没有打印)
- 你得到了 ADBC!
反向示例(您的示例)
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
我希望这对像我这样的初学者足够简单
接受的答案很好,但是,为了简单起见,我想尽量用不同的方式描述它。希望能帮到一些人。
遇到线性化问题,第一步是画出类和traits的hierarchy tree .对于这个特定示例,层次结构树将是这样的:
第二步是写下所有干扰目标问题的traits和类的线性化。在最后一步之前,您将需要它们。为此,您只需编写到达根目录的路径。特征的线性化如下:
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;
重要的注意事项是,它通过首先使用右优先、深度优先搜索来解析方法调用。换句话说,你应该从最右边开始写线性化。它是这样的: 大号(B)>>大号(C)>>大号(D)>>大号(A)
第四步是最简单的一步。只需将每个线性化从第二步替换到第三步即可。替换后,你会得到这样的东西:
B -> A -> C -> B -> A -> D -> A -> A
最后但并非最不重要的,您现在应该从左到右删除所有重复的 类。应删除粗体字符: B -> A -> C -> B -> A -> D -> A -> 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}