when mixin functions for define classes basics scala traits

mixin - scala for



Orden de linealización en Scala (7)

Tengo dificultades para comprender el orden de linealización en Scala cuando trabajo con rasgos:

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???? } }

Imprime CBDA pero no puedo entender por qué. ¿Cómo se determina el orden de los rasgos?

Gracias


explicación, cómo el compilador ve una clase Combined que extiende los rasgos 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); () } };

ejemplo reducido

Puedes leer de izquierda a derecha. Aquí hay un pequeño ejemplo. Los tres rasgos imprimirán su nombre cuando se inicialicen, es decir, se extiendan:

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

Entonces este es el orden básico de linealización. Entonces el último sobrescribirá al anterior.

Tu problema es un poco más complejo. Como sus rasgos ya extienden otros rasgos que anulan algunos valores de los rasgos anteriores. Pero el orden de inicialización de left to right o right will override left .

Debe tener en cuenta que el rasgo en sí se inicializará primero.


Además de otras respuestas, puede encontrar una explicación paso a paso en el resultado del fragmento a continuación.

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>


Bueno, en realidad veo que acabas de invertir la linealización del constructor, que creo que es bastante simple, así que primero comprendamos la linealización del constructor

Primer ejemplo

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

Qué salidas:

ADBC ADBC

Entonces, para calcular la salida, simplemente tomo la clase / rasgos uno por uno de izquierda a derecha y luego escribo las salidas de forma recursiva (sin duplicados) aquí es cómo:

  1. Nuestra firma de clase es: la class X extends A with D with C with B
  2. Entonces, el primero es A, ya que A no tiene padres (deadend) solo imprime su constructor
  3. Ahora D, que extiende A, ya que ya imprimimos A, entonces imprimimos D
  4. Ahora C, que extiende B, que extiende A, entonces omitimos A porque ya se ha impreso, luego imprimimos B, luego imprimimos C (es como una función recursiva)
  5. Ahora B, que extiende A, saltamos A, y también saltamos B (nada impreso)
  6. y tienes ADBC!

Ejemplo invertido (su ejemplo)

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

La salida es:

ADBC CBDA

Espero que haya sido lo suficientemente simple para principiantes como yo


El proceso mediante el cual scala resuelve la súper llamada se llama Linealización. En su ejemplo, crea Object como

var d = new A with D with C with B;

Entonces, como se especifica en los documentos de referencia de Scala, Here llamada a super se resolverá como

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

Ahora comience desde la izquierda y elimine la construcción duplicada en la que la derecha ganará una

por ejemplo, eliminar A y obtenemos

l(A) = B >> C >> B >> D >> A

eliminamos B y obtenemos

l(A) = C >> B >> D >> A

Aquí no tenemos ninguna entrada duplicada. Ahora comienza a llamar desde C

CBDA

super.foo en la clase C llamará a foo en B y foo en B llamará a foo en D y así sucesivamente.

PS aquí l (A) es la linealización de A


La respuesta aceptada es maravillosa, sin embargo, en aras de la simplificación, me gustaría hacer todo lo posible para describirla de una manera diferente. La esperanza puede ayudar a algunas personas.

Cuando encuentre un problema de linealización, el primer paso es dibujar el árbol de jerarquía de las clases y rasgos. Para este ejemplo específico, el árbol de jerarquía sería algo como esto:

El segundo paso es anotar toda la linealización de los rasgos y clases que interfiere con el problema objetivo. Los necesitará todos en el anterior al último paso. Para esto, debe escribir solo la ruta para llegar a la raíz. La linealización de los rasgos son los siguientes:

L(A) = A L(C) = C -> B -> A L(B) = B -> A L(D) = D -> A

El tercer paso es escribir la linealización del problema. En este problema específico, estamos planeando resolver la linealización de

var d = new A with D with C with B;

Una nota importante es que hay una regla por la cual resuelve la invocación de métodos al usar primero la búsqueda derecha primero, profundidad primero. En otras palabras, debe comenzar a escribir la linealización desde el lado derecho. Es como sigue: L (B) >> L (C) >> L (D) >> L (A)

El cuarto paso es el más simple. Simplemente sustituya cada linealización del segundo paso al tercer paso. Después de la sustitución, tendrá algo como esto:

B -> A -> C -> B -> A -> D -> A -> A

Por último, pero no menos importante , ahora debe eliminar todas las clases duplicadas de izquierda a derecha. Deben eliminarse los caracteres en negrita: B -> A -> C -> B -> A -> D -> A -> A

Verá, tiene el resultado: C -> B -> D -> A Por lo tanto, la respuesta es CBDA.

Sé que no es una descripción conceptual profunda individual, pero supongo que puede ayudar como complemento de la descripción conceptual.

Y esta parte se explica confiando en la fórmula:

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}


Los rasgos de Scala se acumulan, por lo que puede verlos agregándolos uno a la vez:

  1. Comience con la new A => foo = "A"
  2. Apilar with D => foo = "DA"
  3. Apilar with C que se apila with B => foo = "CBDA"
  4. Apilar with B no hace nada porque B ya está apilado en C => foo = "CBDA"

Aquí hay una publicación de blog sobre cómo Scala resuelve el problema de la herencia de diamantes.


Una forma intuitiva de razonar sobre la linealización es referirse al orden de construcción y visualizar la jerarquía lineal.

Podrías pensar de esta manera. La clase base se construye primero; pero antes de poder construir la clase base, sus superclases / rasgos deben construirse primero (esto significa que la construcción comienza en la parte superior de la jerarquía). Para cada clase en la jerarquía, los rasgos mezclados se construyen de izquierda a derecha porque un rasgo a la derecha se agrega "más tarde" y, por lo tanto, tiene la posibilidad de "anular" los rasgos anteriores. Sin embargo, de manera similar a las clases, para construir un rasgo, sus rasgos básicos deben construirse primero (obvio); y, razonablemente, si un rasgo ya se ha construido (en cualquier lugar de la jerarquía), no se reconstruye nuevamente. Ahora, el orden de construcción es el reverso de la linealización. Piense en los rasgos / clases "base" como más altos en la jerarquía lineal, y los rasgos más bajos en la jerarquía como más cercanos a la clase / objeto que es el sujeto de la linealización. La linealización afecta cómo se resuelve `super´ en un rasgo: se resolverá al rasgo base más cercano (más alto en la jerarquía).

Así:

var d = new A with D with C with B;

La linealización de A with D with C with B es

  • (parte superior de la jerarquía) A (construido primero como clase base)
  • linealización de D
    • A (no considerado como A ocurre antes)
    • D (D se extiende A)
  • linealización de C
    • A (no considerado como A ocurre antes)
    • B (B se extiende A)
    • C (C se extiende B)
  • linealización de B
    • A (no considerado como A ocurre antes)
    • B (no considerado como B ocurre antes)

Entonces la linealización es: ADBC. Se podría considerar como una jerarquía lineal donde A es la raíz (la más alta) y se construye primero, y C es la hoja (la más baja) y se construye al final. Como C se construye en último lugar, significa que puede anular a los miembros "anteriores".

Dadas estas reglas intuitivas, d.foo llama a C.foo , que devuelve una "C" seguida de super.foo() que se resuelve en B (el rasgo a la izquierda de B , es decir, más alto / antes, en la linealización), que devuelve una "B" seguida de super.foo() que se resuelve en D , que devuelve una "D" seguida de super.foo() que se resuelve en A , que finalmente devuelve "A". Entonces tienes "CBDA".

Como otro ejemplo, preparé el siguiente:

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