Cominciamo con la dipendenza ciclica.
trait A {
selfA: B =>
def fa: Int }
trait B {
selfB: A =>
def fb: String }
Tuttavia, la modularità di questa soluzione non è eccezionale come potrebbe sembrare, poiché è possibile ignorare i tipi di sé in questo modo:
trait A1 extends A {
selfA1: B =>
override def fb = "B's String" }
trait B1 extends B {
selfB1: A =>
override def fa = "A's String" }
val myObj = new A1 with B1
Tuttavia, se si ignora un membro di un tipo di sé, si perde l'accesso al membro originale, a cui è ancora possibile accedere tramite l'ereditarietà super. Quindi ciò che si guadagna davvero usando l'eredità è:
trait AB {
def fa: String
def fb: String }
trait A1 extends AB
{ override def fa = "A's String" }
trait B1 extends AB
{ override def fb = "B's String" }
val myObj = new A1 with B1
Ora non posso pretendere di comprendere tutte le sottigliezze del modello di torta, ma mi sembra che il metodo principale per applicare la modularità sia attraverso la composizione piuttosto che l'ereditarietà o i tipi di sé.
La versione dell'ereditarietà è più breve, ma il motivo principale per cui preferisco l'ereditarietà rispetto ai tipi di sé è che trovo molto più difficile ottenere l'ordine di inizializzazione corretto con i tipi di sé. Tuttavia, ci sono alcune cose che puoi fare con i tipi di sé che non puoi fare con l'eredità. I tipi di sé possono usare un tipo mentre l'ereditarietà richiede un tratto o una classe come in:
trait Outer
{ type T1 }
trait S1
{ selfS1: Outer#T1 => } //Not possible with inheritance.
Puoi anche fare:
trait TypeBuster
{ this: Int with String => }
Anche se non sarai mai in grado di istanziarlo. Non vedo alcun motivo assoluto per non essere in grado di ereditare da un tipo, ma certamente penso che sarebbe utile avere classi e tratti del costruttore di percorsi dato che abbiamo tratti / classi del costruttore di tipi. Sfortunatamente
trait InnerA extends Outer#Inner //Doesn't compile
Abbiamo questo:
trait Outer
{ trait Inner }
trait OuterA extends Outer
{ trait InnerA extends Inner }
trait OuterB extends Outer
{ trait InnerB extends Inner }
trait OuterFinal extends OuterA with OuterB
{ val myV = new InnerA with InnerB }
O questo:
trait Outer
{ trait Inner }
trait InnerA
{this: Outer#Inner =>}
trait InnerB
{this: Outer#Inner =>}
trait OuterFinal extends Outer
{ val myVal = new InnerA with InnerB with Inner }
Un punto che dovrebbe essere più enfatizzato è che i tratti possono estendere le classi. Grazie a David Maclver per averlo segnalato. Ecco un esempio dal mio codice:
class ScnBase extends Frame
abstract class ScnVista[GT <: GeomBase[_ <: TypesD]](geomRI: GT) extends ScnBase with DescripHolder[GT] )
{ val geomR = geomRI }
trait EditScn[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]
trait ScnVistaCyl[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]
ScnBase
eredita dalla classe Swing Frame, quindi potrebbe essere usato come un tipo di auto e poi miscelato alla fine (all'istanza). Tuttavia, val geomR
deve essere inizializzato prima di essere utilizzato ereditando i tratti. Quindi abbiamo bisogno di una classe per far rispettare la precedente inizializzazione di geomR
. La classe ScnVista
può quindi essere ereditata da più tratti ortogonali che possono essere ereditati da loro stessi. L'uso di più parametri di tipo (generici) offre una forma alternativa di modularità.