In che modo i tratti in Scala evitano l '"errore del diamante"?


16

(Nota: ho usato 'errore' invece di 'problema' nel titolo per ovvi motivi ..;)).

Ho fatto alcune letture di base sui Tratti alla Scala. Sono simili alle interfacce in Java o C #, ma consentono l'implementazione predefinita di un metodo.

Mi chiedevo: questo non può causare un caso di "problema del diamante", motivo per cui molte lingue evitano in primo luogo l'eredità multipla?

In tal caso, come gestisce Scala?


Condividere la tua ricerca aiuta tutti . Dicci cosa hai provato e perché non ha soddisfatto le tue esigenze. Ciò dimostra che hai impiegato del tempo per cercare di aiutarti, ci salva dal ribadire risposte ovvie e soprattutto ti aiuta a ottenere una risposta più specifica e pertinente. Vedi anche Come chiedere
moscerino

2
@gnat: questa è una domanda concettuale, non una domanda concreta. Se mi chiedeva "Ho questa lezione alla Scala e mi sta dando problemi che penso possano essere correlati al problema del diamante, come posso risolverlo?" allora il tuo commento sarebbe appropriato, ma poi la domanda apparterrebbe su SO. : P
Mason Wheeler,

@MasonWheeler Ho fatto anche delle letture di base su Scala. E la prima ricerca di "diamante" in quello che ho letto mi ha dato la risposta: "Un tratto ha tutte le caratteristiche del costrutto dell'interfaccia Java. Ma i tratti possono avere metodi implementati su di essi. Se hai familiarità con Ruby, i tratti sono simili ai mixin di Ruby. Puoi mescolare molti tratti in una singola classe. I tratti non possono assumere parametri del costruttore, ma a parte questo si comportano come classi. Ciò ti dà la possibilità di avere qualcosa che si avvicina all'eredità multipla senza il problema del diamante ". La mancanza di sforzo in questa domanda sembra piuttosto palese
moscerino il

7
Leggere quell'affermazione non ti dice COME semplicemente.
Michael Brown,

Risposte:


22

Il problema del diamante è l'incapacità di decidere quale implementazione del metodo scegliere. Scala risolve ciò definendo quale implementazione scegliere come parte delle specifiche del linguaggio ( leggi la parte su Scala in questo articolo di Wikipedia ).

Ovviamente, la stessa definizione di ordine potrebbe essere utilizzata anche nell'eredità multipla di classe, quindi perché preoccuparsi dei tratti?

Il motivo per cui l'IMO è costruttori. I costruttori hanno diversi limiti che i normali metodi non hanno: possono essere chiamati solo una volta per oggetto, devono essere chiamati per ogni nuovo oggetto e il costruttore di una classe figlio deve chiamare il costruttore del genitore come prima istruzione (la maggior parte delle lingue fallo implicitamente per te se non hai bisogno di passare parametri).

Se B e C ereditano A e D ereditano B e C, e entrambi i costruttori di B e C chiamano il costruttore di A, allora il costruttore di D chiamerà il costruttore di A due volte. Definire quali implementazioni scegliere come Scala ha fatto con i metodi non funzionerà qui perché entrambi i costruttori di B e C devono essere chiamati.

I tratti evitano questo problema poiché non hanno costruttori.


1
È possibile utilizzare la linearizzazione C3 per chiamare i costruttori una volta e una sola volta: è così che Python fa l'ereditarietà multipla. Dalla parte superiore della mia testa la linearizzazione per D <B | C <Un diamante è D -> B -> C -> A. Inoltre, una ricerca su google mi ha mostrato che i tratti di Scala possono avere variabili mutabili, quindi sicuramente c'è un costruttore da qualche parte lì dentro? Ma se sta usando la composizione sotto il cofano (non lo so, non ho mai usato Scala) non è difficile vedere che B e C potrebbero condividere su istanza di A ...
Doval

... I tratti sembrano solo un modo molto conciso per esprimere tutta la caldaia che combina ereditarietà dell'interfaccia e composizione + delega, che è il modo corretto di riutilizzare il comportamento.
Doval,

@Doval La mia esperienza di costruttori in Python ereditato in molti casi è che sono un dolore reale. Ogni costruttore non può sapere in quale ordine verrà chiamato, quindi non sa quale sia la firma del suo costruttore principale. La solita soluzione è che ogni costruttore prenda un sacco di argomenti di parole chiave e passi quelli inutilizzati al suo super costruttore, ma se hai bisogno di lavorare con una classe esistente che non segue quella convenzione, non puoi ereditare in modo sicuro da esso.
James_pic,

Un'altra domanda è: perché C ++ non ha scelto una politica sana per il problema dei diamanti?
utente

20

Scala evita il problema dei diamanti con qualcosa chiamato "linearizzazione del tratto". Fondamentalmente, cerca l'implementazione del metodo nei tratti che estendi da destra a sinistra. Esempio semplice:

trait Base {
   def op: String
}

trait Foo extends Base {
   override def op = "foo"
}

trait Bar extends Base {
   override def op = "bar"
}

class A extends Foo with Bar
class B extends Bar with Foo

(new A).op
// res0: String = bar

(new B).op
// res1: String = foo

Detto questo, l'elenco dei tratti che cerca potrebbe contenere più di quelli che hai esplicitamente dato, poiché potrebbero estendere altri tratti. Una spiegazione dettagliata è fornita qui: Tratti come modifiche impilabili e un esempio più completo di linearizzazione qui: Perché non ereditare più?

Credo che in altri linguaggi di programmazione questo comportamento venga talvolta definito "Ordine di risoluzione del metodo" o "MRO".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.