2011-08-27 17 views
6

Se ho il seguente Scala tipo di gerarchia:Un parametro di tipo Scala può fare riferimento a se stesso quando viene utilizzato come parametro per una classe base?

// Base traits 
trait TA[X <: TA[X,Y], Y <: TB[X,Y]] 
trait TB[X <: TA[X,Y], Y <: TB[X,Y]] 
trait TC[X <: TA[X,_]] 

// More specific traits 
trait TI[X <: TI[X,Y], Y <: TJ[X,Y]] extends TA[X,Y] 
trait TJ[X <: TI[X,Y], Y <: TJ[X,Y]] extends TB[X,Y] 
trait TK[X <: TI[X,_]] extends TC[X] 

// Concrete class that should implement TK, but does not compile 
class Z extends TK[TI[_,_]] 

// What is needed to be able to get class Z to compile 
// The reference of X to itself has been removed. 
trait TC2[X <: TA[_,_]] 
trait TK2[X <: TI[_,_]] extends TC2[X] 
class Z2 extends TK2[TI[_,_]] 

TC sarebbe un manager generica di una sorta di TA.

TK sarebbe un gestore più specifico di un TA (TI) più specifico.

Z sarebbe l'implementazione concreta che gestisce qualsiasi oggetto che implementa TI.

Z non è legale, ma Z2 è. Sfortunatamente, TC e TK sono più specifici di TC2 e TK2. Quindi c'è un modo per dichiarare Z usando TC e TK, invece di TC2 e TK2?

[EDIT] Ho omesso di dire nella mia domanda iniziale, che in qualche modo capisco perché Z è sbagliato. Quello che voglio veramente sapere è, se c'è un modo di dire qualcosa del tipo:

class Z3 extends TK[TI[TI,_]] 

risposta

7

Quando si hanno complessi vincoli di tipo reciprocamente ricorsivo, spesso può essere utile vedere se è possibile tradurre il problema in uno equivalente utilizzando invece membri di tipo astratto.Se lo facciamo meccanicamente, la vostra base e tratti più specifici finiscono per assomigliare,

// Base traits 
trait TA { 
    type X <: TA 
    type Y <: TB 
} 
trait TB { 
    type X <: TA 
    type Y <: TB 
} 

trait TC { 
    self => 
    type X <: TA { type X <: self.X } 
} 

// More specific traits 
trait TI extends TA { 
    type X <: TI 
    type Y <: TJ 
} 

trait TJ extends TB { 
    type X <: TI 
    type Y <: TJ 
} 

trait TK { 
    self => 
    type X <: TI { type X <: self.X } 
} 

e ora abbiamo una definizione semplice di Z come,

class Z extends TK { 
    type X = TI 
} 

Nota che le definizioni dei membri del TA, TB e TI, TJ sono essenzialmente gli stessi. Perché questi tipi sono ora di tipo membri possiamo fattorizzare fuori in tipi di base comuni, come così,

// Base traits 
trait T0 { 
    type X <: TA 
    type Y <: TB 
} 
trait TA extends T0 
trait TB extends T0 

trait TC { 
    self => 
    type X <: TA { type X <: self.X } 
} 

// More specific traits 
trait T1 extends T0 { 
    type X <: TI 
    type Y <: TJ 
} 

trait TI extends TA with T1 
trait TJ extends TB with T1 

trait TK extends TC { 
    self => 
    type X <: TI { type X <: self.X } 
} 

class Z extends TK { 
    type X = TI 
} 
+0

Mentre posso seguire il tuo argomento, penso che tu abbia risolto il problema cambiando il significato di Z. Z era un TK, e quindi un TC nel mio esempio, ma non era, e non doveva essere, un TI. Se TC è un "manager", TA è, ad esempio, un ufficio e TB qualcosa legato all'ufficio di cui un manager non si preoccupa, e TK è un gestore contabile e TI un ufficio contabile, quindi Z sarebbe un'istanza concreta di qualcosa che era allo stesso tempo un contabile e un ufficio contabile. Inoltre, voglio sottolineare che nel mio codice reale, TA e TB differirebbero perché definirebbero i propri comportamenti. –

+0

Commento corretto: ho ottimizzato la definizione di Z sopra per affrontarla. Si noti che è possibile aggiungere qualsiasi altra definizione che si desidera TA, TB, TI, TJ ... semplicemente non è necessario ripetere i membri del tipo. –

3

Non sarebbe corretto. Ecco perché, con un esempio semplificato. Non abbiamo bisogno di due parametri generici, non abbiamo bisogno di sottotipi TI, TJ e TK sia

trait TA[X <: TA[X]] 
trait TC[X <: TA[X]] 
class Z extends TC[TA[_]] 

argomenti di tipo [TA [_]] non sono conformi al di tratto parametro di tipo TC limiti [X < : TA [X]]

Vediamo perché questa dichiarazione non è corretta ed è corretto che non funzioni. Aggiungiamo un po 'di codice. Cambio TC in un class, in modo che il codice possa essere tradotto in java. A trait farebbe altrettanto bene in scala.

trait TA[X <: TA[X]] {def f(x: X) } 
class TC[X <: TA[X]] {def g(x: X) = x.f(x)} 

Funziona bene, è anche x: XTA[X], quindi non ha una routine f, che accettare un X.

Se cerchiamo invece

class TC2[X <: TA[_]] {def g(x: X) = x.f(x)} 

poi fallisce. Sappiamo che esiste un metodo f su x, ma non sappiamo quale tipo sia necessario come argomento, non possiamo sapere che x sarà ok. Infatti, supponiamo definiamo

class T1 extends TA[T1]] {def f(t1: T1) = {}; def t1Only = println("only in T1")} 
class T2 extends TA[T1]] {def f(t1: T1) = t1.t1Only } 

Ora, se TC2 è stato permesso, ho potuto creare un TC2[T2], chiamare g con una T2, che rimetterebbe in fT2 con un T2. Questo non è permesso, e giustamente in quanto T2 non ha metodo t1Only.

Questo spiega perché TC non può essere accettato TA[_]] come parametro, come che consentirebbe T2, che non è compatibile con il metodo g. E quindi perché non possiamo definire Z con il parametro TA[_]. Sarebbe lo stesso in java e con il tuo codice originale.


Edit: Mi sento un po 'in colpa per la mia risposta. Con la ragione che ho dato perché non dovrebbe essere permesso, ho pensato che ci sarebbe stata una soluzione semplice. Ha fallito, non ho avuto il tempo di indagare ulteriormente, e pubblicato senza menzionarlo. La soluzione alternativa era di tipo self. Se facciamo

trait TA[X <: TA[X]] {self: X => } 

allora non possiamo definire T2 extends TA[T1]. Quindi è più limitato del codice originale. Ma dobbiamo comunque accettare limitazioni al codice originale, perché non era corretto. Quindi non può essere solo un trucco di sintassi, deve rendere le cose impossibili che non lo sono. Pensavo che lo T2 extends TA[T1] non fosse qualcosa che era previsto, e che era l'unica cosa da prevenire.

Apparentemente, non è stato, lo stesso errore. Ora non ho un esempio perché non dovrebbe funzionare. Che ovviamente non significa che non ce n'è.

Ho quindi esaminato la soluzione di Miles, chiedendomi perché la possibilità di T2 extends TA[T1] non leda. Quindi, di nuovo, scartando TB, Y, TI, TJ, e TK:

trait TA{type X} 
trait TC{self => type X <: TA{type X <: self.X} 
class Z extends TC{type X = TA} 

Questo compila. E possiamo fare

trait T1 extends TA{type X = T1} 
trait T2 extends TA{type X = T1} 

ma c'è una cosa che non possiamo fare:

trait TA {type X <: TA; def f(x: X)} 
trait TC {self => 
    type X <: TA{type X <: self.X} 
    def g(x: X) = x.f(x) 
} 

Otteniamo il seguente errore su g, per il x argomento per f

tipo non corrispondente; trovati: x.type (con tipo sottostante TC.this.X) richiesto: x.x

TC non è esattamente quella originale (come originariamente, g era permesso). È così perché c'è uno type X <: self.X su TA mentre TC[X <: TA[X]] era il più forte type X = self.X. Se scriviamo che, invece, siamo tornati all'errore originale, Z non viene compilato. Quindi questo TC è un po 'tra lo TC originale (type X = self.X) e lo TC2 (nessuna conoscenza della X di TA). Di nuovo, una limitazione sul codice originale, non possiamo definire g.

Se la limitazione è accettabile, tutto ok. Non so come scriverlo come un generico (né come scrivere il tipo self {self : X => con un membro di tipo astratto). Miles è sicuramente l'esperto, sono sicuro che sarebbe in grado di dire come è fatto o che non è possibile.

+0

Miles ha mostrato come potrebbe funzionare con i tipi astratti, mentre hai mostrato il motivo per cui non ha funzionato con i generici. Mentre entrambe le risposte sono utili, nessuno dei due mi dice se "la classe Z3 estende TK [TI [TI, _]]" è effettivamente possibile come generici, e come. Dato che i generici e i tipi astratti sono equivalenti in Scala, sapresti come farlo correttamente con i generici? –

+0

Purtroppo, la risposta breve è che non lo so. Non sono sicuro che siano equivalenti al 100%. Non sarei stato in grado di convertire i membri del tipo come ha fatto @Miles. Risposta lunga come una modifica. –

+0

Non conosco alcun modo pratico per codificare la ricorsione in cui ci si trova oltre che utilizzando i membri del tipo. Generici e membri del tipo non sono in realtà completamente equivalenti, e questa è una delle situazioni che li differenzia. –

Problemi correlati