web-dev-qa-db-ja.com

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と表示されますが、その理由がわかりません。特性の順序はどのように決定されますか?

THX

42
woodtluk

線形化を推論する直感的な方法は、構築順序を参照して線形階層を視覚化することです。

あなたはこのように考えることができます。基本クラスが最初に作成されます。ただし、基本クラスを構築する前に、まずスーパークラス/特性を構築する必要があります(つまり、構築は階層の最上位から始まります)。階層内のクラスごとに、混合された特性は左から右に構築されます。これは、右側の特性が「後で」追加され、以前の特性を「オーバーライド」する可能性があるためです。ただし、クラスと同様に、特性を構築するには、その基本特性を最初に構築する必要があります(自明)。そして、かなり合理的に、トレイトがすでに(階層内のどこかに)構築されている場合、それは再び再構築されません。現在、構築順序は線形化の逆です。 「基本」特性/クラスを線形階層でより高いものと考え、線形化の対象であるクラス/オブジェクトに近いほど、階層でより低い特性を考えてください。線形化は、特性で「スーパー」がどのように解決されるかに影響します。これは、最も近い基本特性(階層の上位)に解決されます。

したがって:

_var d = new A with D with C with B;
_

_A with D with C with B_の線形化は

  • (階層の最上位)A(基本クラスとして最初に作成)
  • dの線形化
    • A (not considered as A occurs before)
    • D (D extends A)
  • cの線形化
    • A (not considered as A occurs before)
    • B (B extends A)
    • C (C extends B)
  • bの線形化
    • A (not considered as A occurs before)
    • B (not considered as B occurs before)

したがって、線形化はA-D-B-Cです。これは、Aがルート(最高)で最初に作成され、Cがリーフ(最低)で最後に作成される線形階層と考えることができます。 Cは最後に作成されるため、「前の」メンバーをオーバーライドできることを意味します。

これらの直感的なルールを前提として、_d.foo_は_C.foo_を呼び出し、 "C"に続いてsuper.foo()を返し、BBの左側の特性、つまり上位/前) 、線形化では) "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
_
48
metaphori

受け入れられた答えは素晴らしいですが、簡単にするために、私はそれを別の方法で説明するために最善を尽くしたいと思います。希望は一部の人々を助けることができます。

線形化の問題が発生した場合、最初のステップはクラスの階層ツリーを描画し、特性。この特定の例では、階層ツリーは次のようになります。

enter image description here

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}
15
Mehran

Scalaの特性はスタックしているので、一度に1つずつ追加してそれらを見ることができます。

  1. new Aで始まる=> foo = "A"
  2. スタックwith D => foo = "DA"
  3. スタックwith Cスタックwith B => foo = "CBDA"
  4. スタックwith Bは、BCに既にスタックされているため、何もしません=> foo = "CBDA"

ここに ブログ投稿 があり、Scalaがダイヤモンドの継承問題を解決する方法について説明しています。

12
Noah

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の線形化です)

6
optional

他の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>
1
Odomontois

説明、コンパイラがクラスをどのように認識するか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

トレイト自体が最初に初期化されることを覚えておく必要があります。

1
Andreas Neumann

さて、実際には、コンストラクタの線形化を逆にしただけだと思います。これは非常に単純だと思うので、まず、コンストラクタの線形化を理解しましょう。

最初の例

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つずつ取得し、再帰的に(重複なしで)出力を記述します。方法は次のとおりです。

  1. クラスの署名は:class X extends A with D with C with B
  2. つまり、最初のAはAです。Aには親がないため(デッドエンド)、コンストラクターを出力するだけです。
  3. ここでDはAを拡張したものです。すでにAを印刷しているので、Dを印刷しましょう。
  4. Aを拡張するBを拡張するCです。Aは既に印刷されているのでスキップし、次にBを印刷してからCを印刷します(これは再帰的な関数のようなものです)
  5. 今度はBがAを拡張して、Aをスキップし、Bもスキップします(何も印刷されません)。
  6. そして、あなたは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

私のような初心者にとっては十分簡単だったと思います

0
Ismail Marmoush