トレイトを使用する場合、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
線形化を推論する直感的な方法は、構築順序を参照して線形階層を視覚化することです。
あなたはこのように考えることができます。基本クラスが最初に作成されます。ただし、基本クラスを構築する前に、まずスーパークラス/特性を構築する必要があります(つまり、構築は階層の最上位から始まります)。階層内のクラスごとに、混合された特性は左から右に構築されます。これは、右側の特性が「後で」追加され、以前の特性を「オーバーライド」する可能性があるためです。ただし、クラスと同様に、特性を構築するには、その基本特性を最初に構築する必要があります(自明)。そして、かなり合理的に、トレイトがすでに(階層内のどこかに)構築されている場合、それは再び再構築されません。現在、構築順序は線形化の逆です。 「基本」特性/クラスを線形階層でより高いものと考え、線形化の対象であるクラス/オブジェクトに近いほど、階層でより低い特性を考えてください。線形化は、特性で「スーパー」がどのように解決されるかに影響します。これは、最も近い基本特性(階層の上位)に解決されます。
したがって:
_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
_
受け入れられた答えは素晴らしいですが、簡単にするために、私はそれを別の方法で説明するために最善を尽くしたいと思います。希望は一部の人々を助けることができます。
線形化の問題が発生した場合、最初のステップはクラスの階層ツリーを描画し、特性。この特定の例では、階層ツリーは次のようになります。
2番目のステップは、ターゲットの問題に干渉する特性とクラスのすべての線形化を書き留めます。あなたは最後のステップの前にそれらのすべてを必要とします。そのためには、ルートに到達するためのパスのみを記述する必要があります。特性の線形化は次のとおりです。
L(A) = A
L(C) = C -> B -> A
L(B) = B -> A
L(D) = D -> A
3番目のステップは、問題の線形化を記述することです。この特定の問題では、の線形化を解決することを計画しています
var d = new A with D with C with B;
重要な注意事項は、最初に右優先、深さ優先の検索を使用してメソッド呼び出しを解決するルールがあることです。別の言葉で言えば、あなたは最も右側から線形化を書き始めるべきです。それは次の通りです:L(B)>> L(C)>> L(D)>> L(A)
4番目のステップが最も単純なステップです。 2番目のステップから3番目のステップまでの各線形化を置き換えるだけです。置換後は、次のようになります。
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の特性はスタックしているので、一度に1つずつ追加してそれらを見ることができます。
new A
で始まる=> foo = "A"
with D
=> foo = "DA"
with C
スタックwith B
=> foo = "CBDA"
with B
は、B
がC
に既にスタックされているため、何もしません=> foo = "CBDA"
ここに ブログ投稿 があり、Scalaがダイヤモンドの継承問題を解決する方法について説明しています。
scalaスーパーコールを解決するプロセスが呼び出されますLinearizationこの例では、次のようにオブジェクトを作成します
var d = new A with D with C with B;
したがって、指定したようにscala reference docs 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を呼び出し、BのfooはD
のfooを呼び出します。
P.S。ここでl(A)はAの線形化です)
他のanwserに加えて、以下のスニペット結果に段階的な説明があります
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
特性を拡張する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);
()
}
};
左から右へ読むことができます。これは小さな例です。 3つの特性は、初期化、つまり拡張されたときに名前を出力します。
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
したがって、出力を計算するには、クラス/特性を左から右に1つずつ取得し、再帰的に(重複なしで)出力を記述します。方法は次のとおりです。
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
私のような初心者にとっては十分簡単だったと思います