Una mia amica ha posto una domanda di lingua Scala apparentemente innocua la settimana scorsa a cui non ho avuto una buona risposta: se esiste un modo semplice per dichiarare una raccolta di elementi appartenenti a una comune classe di tipi . Ovviamente non esiste una nozione di classe "typeclass" di prima classe in Scala, quindi dobbiamo pensarci in termini di tratti e limiti di contesto (cioè impliciti).Tipi di impressioni rispetto al vecchio sottotipo vecchio
In concreto, dato qualche tratto T[_]
che rappresenta una typeclass, e tipi A
, B
e C
, con impliciti corrispondenti portata T[A]
, T[B]
e T[C]
, vogliamo dichiarare qualcosa di simile a un List[T[a] forAll { type a }]
, in cui siamo in grado di gettare le istanze di A
, B
e C
con impunità. Questo ovviamente non esiste in Scala; uno question last year ne parla in modo più approfondito.
La domanda di follow-up naturale è "come fa Haskell a farlo?" Bene, GHC in particolare ha un'estensione di sistema di tipo denominata impredicative polymorphism, descritta nella carta "Boxy Types". In breve, data una classe di caratteri T
si può legalmente costruire un elenco [forall a. T a => a]
. Data una dichiarazione di questo modulo, il compilatore esegue alcune magie di passaggio del dizionario che ci consentono di conservare le istanze di typeclass corrispondenti ai tipi di ciascun valore nell'elenco in fase di esecuzione.
Il fatto è che "la magia del dizionario" somiglia molto a "vtables". In un linguaggio orientato agli oggetti come Scala, la sottotipizzazione è un meccanismo molto più semplice e naturale rispetto all'approccio "Tipi Boxy". Se il nostro A
, B
e C
estendono tutti i tratti T
, allora possiamo semplicemente dichiarare List[T]
ed essere felici. Allo stesso modo, come nota Miles in un commento qui sotto, se tutti estendono i tratti T1
, T2
e T3
, allora posso usare List[T1 with T2 with T3]
come equivalente all'imprevedibile Haskell [forall a. (T1 a, T2 a, T3 a) => a]
.
Tuttavia, il principale svantaggio noto con sottotipo rispetto al typeclasses è accoppiamento stretto: i miei A
, B
e C
tipi devono avere il loro T
comportamento cotto in Supponiamo che questo è un importante dealbreaker, e io può. 't utilizza la sottotipizzazione. Così la terra di mezzo in Scala è ruffiani^conversioni H^H^H^H^Himplicit: dato qualche A => T
, B => T
e C => T
portata implicita, posso di nuovo tranquillamente popolare un List[T]
con i miei A
, B
e C
valori ...
... Fino a quando non vogliamo List[T1 with T2 with T3]
. A quel punto, anche se abbiamo conversioni implicite A => T1
, A => T2
e A => T3
, non possiamo inserire un A
nell'elenco. Potremmo ristrutturare le nostre conversioni implicite per fornire letteralmente lo A => T1 with T2 with T3
, ma non ho mai visto nessuno farlo prima e sembra ancora un'altra forma di accoppiamento stretto.
Va bene, quindi la mia domanda è, infine, suppongo, una combinazione di un paio di domande che sono state in precedenza chiesto qui: "why avoid subtyping?" e "advantages of subtyping over typeclasses" ... c'è qualche teoria unificante che dice il polimorfismo impredicativa e sottotipo il polimorfismo sono la stessa cosa ? Le conversioni implicite sono in qualche modo il segreto amore-figlio dei due? E qualcuno può articolare un modello buono e pulito per esprimere limiti multipli (come nell'ultimo esempio sopra) in Scala?
Questo potrebbe essere rilevante: http://stackoverflow.com/questions/7213676/forall-in-scala – missingfaktor
@missingfaktor lo è certamente, è per questo che l'ho collegato! – mergeconflict
Aah, mi dispiace, l'ho perso in prima lettura! – missingfaktor