在处理特征时,我很难理解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
答案 0 :(得分:42)
推理线性化的直观方法是参考构造顺序并可视化线性层次结构。
你可以这么想。首先构造基类;但在能够构造基类之前,必须首先构造其超类/特征(这意味着构造从层次结构的顶部开始)。对于层次结构中的每个类,混合特征从左到右构造,因为右侧的特征“稍后”添加,因此有机会“覆盖”先前的特征。然而,与类相似,为了构造特征,必须首先构建其基本特征(显而易见);并且,相当合理的是,如果已经构建了特征(层次结构中的任何位置),则不再重建特征。现在,施工顺序与线性化相反。将“基础”特征/类视为线性层次结构中较高的特征,并且层次结构中较低的特征更接近于作为线性化主题的类/对象。 线性化会影响特征中“超级”的解析方式:它将解析为最接近的基本特征(层次结构中较高)。
因此:
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()
,该{C}在B
上解析(B
上的特征{1}},即更高/之前,在线性化中),返回“B”后跟super.foo()
,在D
上解析,返回“D”后跟{{1}在super.foo()
上解析,最终返回“A”。所以你有“CBDA”。
另一个例子,我准备了以下一个:
A
答案 1 :(得分:12)
Scala的特征堆栈,所以你可以通过一次添加一个来查看它们:
[include]
../node_modules/ramda
=>开始new A
foo = "A"
=> with D
foo = "DA"
=>的堆栈with C
with B
foo = "CBDA"
不执行任何操作,因为with B
已经堆叠在B
=>中C
这里是关于Scala如何解决钻石继承问题的blog post。
答案 2 :(得分:12)
接受的答案很精彩,但是,为了简化起见,我想以不同的方式尽力描述它。希望可以帮助一些人。
遇到线性化问题时, 第一步 是绘制类和特征的层次结构树。对于此特定示例,层次结构树将是这样的:
第二步 是写下干扰目标问题的特征和类的所有线性化。在最后一步之前,你将需要它们。为此,您只需要编写到达根目录的路径。特征的线性化如下:
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
所以要计算输出我只是从左到右依次取类/特征然后递归写输出(没有重复)这里是如何:
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
我希望对于像我这样的初学者来说这很简单