使用特征时,我很难理解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
但我无法弄清楚为什么。如何确定特征的顺序?
谢谢
推理线性化的直观方法是参考构造顺序并可视化线性层次结构。
你可以这么想。首先构造基类;但在能够构造基类之前,必须首先构造其超类/特征(这意味着构造从层次结构的顶部开始)。对于层次结构中的每个类,混合特征从左到右构造,因为右侧的特征“稍后”添加,因此有机会“覆盖”先前的特征。然而,与类相似,为了构造特征,必须首先构建其基本特征(显而易见);并且,相当合理的是,如果已经构建了特征(层次结构中的任何位置),则不再重建特征。现在,施工顺序与线性化相反。将“基础”特征/类视为线性层次结构中较高的特征,并且层次结构中较低的特征更接近于作为线性化主题的类/对象。线性化会影响特征中“超级”的解析方式:它将解析为最接近的基本特征(层次结构中较高)。
从而:
var d = new A with D with C with B;
A with D with C with B
的线性化是
因此线性化是:A-D-B-C。您可以将其视为线性层次结构,其中A是根(最高)并且首先构造,C是叶(最低)并且最后构造。由于C是最后构造的,这意味着可以覆盖“先前”成员。
鉴于这些直观的规则,d.foo
调用C.foo
,它返回一个“C”,然后是super.foo()
,它在B
(B
左边的特征,即线性化中的更高/之前的特征)上解析,返回“B”,然后返回在super.foo()
上解决的D
,它返回一个“D”,然后是super.foo()
,它在A
上解析,最终返回“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
Scala的特征堆栈,所以你可以通过一次添加一个来查看它们:
new A
=> foo = "A"
开始with D
=> foo = "DA"
with C
堆叠with B
=> foo = "CBDA"
with B
什么都不做因为B
已经堆积在C
=> foo = "CBDA"
这是关于Scala如何解决钻石遗传问题的blog post。
接受的答案很精彩,但为了简化起见,我想以不同的方式尽力描述它。希望可以帮助一些人。
遇到线性化问题时,第一步是绘制类和特征的层次结构树。对于此特定示例,层次结构树将是这样的:
第二步是记下干扰目标问题的特征和类的所有线性化。在最后一步之前,你将需要它们。为此,您只需要编写到达根目录的路径。特征的线性化如下:
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 - > 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解析超级调用的过程称为线性化。在您的示例中,您将对象创建为
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.
类C
中的super.foo将在B
中调用foo,在D
中调用B调用foo,等等。
附:这里l(A)是A的线性化
除了其他的anwsers,您还可以在下面的代码段结果中找到分步说明
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>
Combined
which extends the traits 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
或right will override left
。
你必须记住,特征本身将首先被初始化。
实际上,我看到你刚刚颠倒了构造函数的线性化,我认为这很简单,所以首先让我们理解构造函数的线性化
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 X extends A with D with C with B
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
我希望对于像我这样的初学者来说这很简单