Dal momento che nessun altro ha risposto alla domanda, penso che ci proverò io stesso. Devo diventare un po 'filosofico.
La programmazione generica si basa sull'astrazione su tipi simili, senza la perdita di 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 (un insieme 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 la sua interfaccia, definita come parte del suo tipo. Poiché tutte le classi List<T>
condividono la stessa interfaccia, è possibile scrivere codice che funzioni indipendentemente dalla T
scelta. Un altro modo per imporre un'interfaccia è un vincolo di eredità, e sebbene i due sembrino diversi, sono un po 'simili se ci pensate.
Nella maggior parte dei linguaggi orientati agli oggetti, List<>
non è un tipo appropriato in sé. Non ha metodi e quindi non ha interfaccia. È solo questo List<T>
che ha queste cose. In sostanza, in termini più tecnici, gli unici tipi su cui puoi astrarre in modo significativo sono quelli con il tipo *
. Per utilizzare tipi di tipo superiore in un mondo orientato agli oggetti, è necessario formulare i vincoli di tipo in modo coerente con questa restrizione.
Ad esempio, come menzionato nei commenti, possiamo vedere Option<>
e List<>
come "mappabile", nel senso che se hai una funzione, potresti convertire un Option<T>
in un Option<S>
, o un List<T>
in List<S>
. Ricordando che le classi non possono essere utilizzate per astrarre direttamente su tipi di tipo superiore, creiamo invece un'interfaccia:
IMappable<K<_>, T> where K<T> : IMappable<K<_>, T>
E quindi implementiamo l'interfaccia in entrambi List<T>
e Option<T>
come IMappable<List<_>, T>
e IMappable<Option<_>, T>
rispettivamente. Quello che abbiamo fatto è usare tipi di tipo superiore per porre vincoli sui tipi effettivi (di tipo non superiore) Option<T>
e List<T>
. È così che si fa in Scala, anche se ovviamente Scala ha caratteristiche come tratti, variabili di tipo e parametri impliciti che lo rendono più espressivo.
In altre lingue, è possibile astrarre direttamente su tipi di tipo superiore. In Haskell, una delle più alte autorità sui sistemi di tipi, possiamo pronunciare una classe di tipo per qualsiasi tipo, anche se ha un tipo più elevato. Per 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 un mp<a>
in mp<b>
. Possiamo quindi scrivere funzioni che vincolano tipi di tipo più elevato, Mappable
proprio come nei linguaggi orientati agli oggetti è possibile inserire un vincolo di ereditarietà. Beh, in un certo senso.
Per riassumere, la tua capacità di utilizzare tipi di tipo superiore dipende dalla tua capacità di vincolarli o di usarli come parte dei vincoli di tipo.