Poiché nessun altro ha risposto alla domanda, penso che farò un tentativo io stesso. Dovrò diventare un po 'filosofico.
La programmazione generica riguarda l'astrazione su tipi simili, senza la perdita delle informazioni sul tipo (che è ciò che accade con il polimorfismo del valore orientato agli oggetti). Per fare ciò, i tipi devono necessariamente condividere una sorta di interfaccia (una serie di operazioni, non il termine OO) che è possibile utilizzare.
Nei linguaggi orientati agli oggetti, i tipi soddisfano un'interfaccia in virtù delle classi. Ogni classe ha una propria interfaccia, definita come parte del suo tipo. Poiché tutte le classi List<T>
condividono la stessa interfaccia, puoi scrivere codice che funziona indipendentemente dal T
scelto. Un altro modo per imporre un'interfaccia è un vincolo di ereditarietà, e anche se i due sembrano diversi, sono simili se ci pensi.
Nella maggior parte dei linguaggi orientati agli oggetti, List<>
non è un tipo corretto in sé. Non ha metodi e quindi non ha interfaccia. È solo List<T>
che ha queste cose. Essenzialmente, in termini più tecnici, gli unici tipi su cui puoi significativamente passare sopra sono quelli con il tipo *
. Per utilizzare tipi di tipo superiore in un mondo orientato agli oggetti, devi applicare i vincoli del tipo di frase in modo coerente con questa restrizione.
Ad esempio, come menzionato nei commenti, possiamo visualizzare Option<>
e List<>
come "mappabili", nel senso che se hai una funzione, puoi convertire Option<T>
in Option<S>
, oppure List<T>
in List<S>
. Ricordando che le classi non possono essere utilizzate per astrarre direttamente i tipi più elevati, facciamo invece un'interfaccia:
IMappable<K<_>, T> where K<T> : IMappable<K<_>, T>
Quindi implementiamo l'interfaccia in List<T>
e Option<T>
come IMappable<List<_>, T>
e IMappable<Option<_>, T>
rispettivamente. Quello che abbiamo fatto, sta usando tipi di tipo superiore per posizionare vincoli sui tipi reali (non di tipo superiore) Option<T>
e List<T>
. Questo è come è fatto in Scala, anche se naturalmente Scala ha caratteristiche come tratti, variabili di tipo e parametri impliciti che lo rendono più espressivo.
In altre lingue, è possibile astrarre direttamente i tipi più elevati. In Haskell, una delle più alte autorità sui sistemi di tipi, possiamo esprimere una classe di tipo per qualsiasi tipo, anche se ha un tipo più elevato. Ad esempio,
class Mappable mp where
map :: mp a -> mp b
Questo è un vincolo posto direttamente su un tipo (non specificato) mp
che accetta un parametro di tipo e richiede che sia associato alla funzione map
che trasforma mp<a>
in mp<b>
. Possiamo quindi scrivere funzioni che vincolano i tipi di tipo superiore di Mappable
proprio come nei linguaggi orientati agli oggetti potresti inserire un vincolo di ereditarietà. Bene, specie di.
Per riassumere le cose, la tua capacità di utilizzare tipi di tipo più elevato dipende dalla tua capacità di vincolarli o di usarli come parte dei vincoli di tipo.