Ordine di linearizzazione in Scala

Ho difficoltà a capire l’ordine di linearizzazione in Scala quando si lavora con i tratti:

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 ma non riesco a capire perché. Come viene determinato l’ordine dei tratti?

Grazie

Un modo intuitivo per ragionare sulla linearizzazione è fare riferimento all’ordine di costruzione e visualizzare la gerarchia lineare.

Potresti pensare in questo modo. La class base è costruita per prima; ma prima di essere in grado di build la class base, le sue superclassi / caratteristiche devono essere costruite prima (questo significa che la costruzione inizia nella parte superiore della gerarchia). Per ogni class nella gerarchia, i tratti misti sono costruiti da sinistra a destra perché un tratto a destra viene aggiunto “più tardi” e quindi ha la possibilità di “scavalcare” i tratti precedenti. Tuttavia, analogamente alle classi, per build un tratto, i suoi tratti di base devono essere costruiti prima (ovvio); e, abbastanza ragionevolmente, se un tratto è già stato costruito (ovunque nella gerarchia), non viene ricostruito di nuovo. Ora, l’ordine di costruzione è il contrario della linearizzazione. Pensa a tratti / classi “base” più alti nella gerarchia lineare, e ai tratti più bassi nella gerarchia più vicini alla class / object che è il sobject della linearizzazione. La linearizzazione influisce su come “super” è risolto in un tratto: si risolverà con il tratto base più vicino (più in alto nella gerarchia).

Così:

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

La linearizzazione di A with D with C with B è

  • (parte superiore della gerarchia) A (costruito prima come class base)
  • linearizzazione di D
    • A (non considerato come A si verifica prima)
    • D (D estende A)
  • linearizzazione di C
    • A (non considerato come A si verifica prima)
    • B (B estende A)
    • C (C estende B)
  • linearizzazione di B
    • A (non considerato come A si verifica prima)
    • B (non considerato come B si verifica prima)

Quindi la linearizzazione è: ADBC. Si potrebbe pensare ad esso come una gerarchia lineare in cui A è la radice (il più alto) e viene costruita per prima, e C è la foglia (più bassa) e costruita per ultima. Siccome C è costruito per ultimo, significa che potrebbe ignorare i membri “precedenti”.

Date queste regole intuitive, d.foo chiama C.foo , che restituisce una “C” seguita da super.foo() che viene risolto su B (il tratto a sinistra di B , cioè più in alto / prima, nella linearizzazione), che restituisce una “B” seguita da super.foo() che viene risolto su D , che restituisce una “D” seguita da super.foo() che viene risolto su A , che alla fine restituisce “A”. Quindi hai “CBDA”.

Come altro esempio, ho preparato il seguente:

 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 // XARHTSB (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 

I tratti di Scala si impilano, quindi puoi guardarli aggiungendoli uno alla volta:

  1. Inizia con new A => foo = "A"
  2. Stack with D => foo = "DA"
  3. Impila with C che impila with B => foo = "CBDA"
  4. Lo stack with B non fa nulla perché B è già impilato in C => foo = "CBDA"

Ecco un post sul blog su come Scala risolve il problema dell’ereditarietà dei diamanti.

Il processo mediante il quale scala risolve la super chiamata è chiamato Linearizzazione Nel tuo esempio crei Oggetto come

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

Quindi, come documento di riferimento scala specificato Qui chiamata a super sarà risolto come

 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 

Ora inizia da sinistra e rimuovi il costrutto duplicato in cui il destro ne vincerà uno

ad esempio rimuovere A e otteniamo

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

rimuovi B e otteniamo

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

Qui non abbiamo alcuna voce duplicata Ora inizia a chiamare da C

CBDA

super.foo in class C chiamerà foo in B e foo in B call foo in D e così via.

PS qui l (A) è linearizzazione di A

La risposta accettata è meravigliosa, tuttavia, per semplificazione, vorrei fare del mio meglio per descriverlo, in un modo diverso. La speranza può aiutare alcune persone.

Quando incontri un problema di linearizzazione, il primo passo è disegnare l’albero gerarchico delle classi e dei tratti. Per questo specifico esempio, l’albero gerarchico sarebbe qualcosa del genere:

inserisci la descrizione dell'immagine qui

Il secondo passo è quello di scrivere tutta la linearizzazione dei tratti e delle classi che interferiscono con il problema di destinazione. Ne avrai bisogno tutti in uno prima dell’ultimo passaggio. Per questo, è necessario scrivere solo il percorso per raggiungere la radice. La linearizzazione dei tratti è la seguente:

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

Il terzo passo è scrivere la linearizzazione del problema. In questo specifico problema, stiamo progettando di risolvere la linearizzazione di

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

Nota importante: esiste una regola con la quale risolve l’invocazione del metodo usando prima la ricerca first-right, depth-first. In un’altra parola, dovresti iniziare a scrivere la linearizzazione dalla maggior parte destra. È come segue: L (B) >> L (C) >> L (D) >> L (A)

Quarto passo è il passo più semplice. Basta sostituire ogni linearizzazione dalla seconda fase alla terza fase. Dopo la sostituzione, avrai qualcosa di simile a questo:

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

Ultimo ma non meno importante , ora dovresti rimuovere tutte le classi duplicate da sinistra a destra. I caratteri in grassetto devono essere rimossi: D -> A -> C -> B -> A -> D -> A -> A

Vedete, avete il risultato: C -> B -> D -> A Quindi la risposta è CBDA.

So che non è una descrizione concettuale profondamente individuale, ma può essere d’aiuto come complemento per la descrizione concettuale che immagino.

Oltre ad altre anwser puoi trovare spiegazioni dettagliate nel risultato del frammento di seguito

 hljs.initHighlightingOnLoad(); 
    
Expression type foo() result
 new A 
 A 
"A"
 new A with D 
 D 
"DA"
 new A with D with C 
 D with C 
"CBDA"
 new A with D with C with B 
 D with C 
"CBDA"

spiegazione, come il compilatore vede una class Combined che estende i tratti A with D with C with B

 class Combined extends A with D with C with B { final   def super$foo(): String = B$class.foo(Combined.this); override def foo(): String = C$class.foo(Combined.this); final   def super$foo(): String = D$class.foo(Combined.this); final   def super$foo(): String = Combined.super.foo(); def (): Combined = { Combined.super.(); D$class./*D$class*/$init$(Combined.this); B$class./*B$class*/$init$(Combined.this); C$class./*C$class*/$init$(Combined.this); () } }; 

esempio ridotto

Puoi leggere da sinistra a destra. Ecco un piccolo esempio. I tre tratti stamperanno il loro nome una volta inizializzati ovvero estesi:

 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 = [email protected] scala> new A with C with B A C B res1: A with C with B = [email protected] 

Quindi questo è l’ordine di linearizzazione di base. Quindi l’ultimo sovrascriverà il precedente.

Il tuo problema è un po ‘più complesso. Mentre i tratti già estendono altri tratti che essi stessi annullano alcuni valori dei tratti precedenti. Ma l’ordine di inizializzazione left to right o a right will override left .

Devi tenere a mente che il tratto stesso verrà inizializzato per primo.

Beh, in realtà vedo che hai appena invertito la linearizzazione del Costruttore, che credo sia piuttosto semplice, quindi prima di tutto capiamo la linearizzazione del costruttore

Primo esempio

 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 

Quali uscite:

 ADBC ADBC 

Quindi, per calcolare l’output, prendo solo la class / i tratti uno a uno da sinistra a destra, quindi scrivo ricorsivamente gli output (senza duplicati) ecco come:

  1. La nostra firma di class è: la class X extends A with D with C with B
  2. Quindi il primo è A, dal momento che A non ha genitori (deadend), basta stampare il suo costruttore
  3. Ora D, che estende A, poiché abbiamo già stampato A, allora stampiamo D
  4. Ora C, che estende B, che estende A, quindi saltiamo A perché è già stato stampato, quindi stampiamo B, quindi stampiamo C (è come una funzione ricorsiva)
  5. Ora B, che estende A, saltiamo A, e saltiamo anche B (niente stampato)
  6. e tu hai ADBC!

Esempio rovesciato (il tuo esempio)

 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 

L’output è:

 ADBC CBDA 

Spero che sia stato abbastanza semplice per i principianti come me