Perché hai bisogno di tipi superiori?


13

Alcuni linguaggi consentono classi e funzioni con parametri di tipo (come List<T>dove Tpuò essere un tipo arbitrario). Ad esempio, puoi avere una funzione come:

List<S> Function<S, T>(List<T> list)

Alcune lingue consentono tuttavia di estendere questo concetto di un livello superiore, consentendoti di avere una funzione con la firma:

K<S> Function<K<_>, S, T>(K<T> arg)

Dove di per K<_>sé è un tipo come List<_>quello ha un parametro di tipo. Questo "tipo parziale" è noto come costruttore di tipi.

La mia domanda è: perché hai bisogno di questa capacità? Ha senso avere un tipo come List<T>perché tutti List<T>sono quasi esattamente uguali, ma tutto K<_>può essere completamente diverso. Puoi avere un Option<_>e un List<_>che non hanno alcuna funzionalità comune.


7
Ci sono diverse risposte buone qui: stackoverflow.com/questions/21170493/...
itsbruce

3
@itsbruce In particolare, l' Functoresempio nella risposta di Luis Casillas è abbastanza intuitivo. Cosa hanno List<T>e Option<T>hanno in comune? Se mi dai una o una funzione, T -> Sposso darti una List<S>o Option<S>. Un'altra cosa che hanno in comune è che puoi provare a ottenere un Tvalore da entrambi.
Doval

@Doval: come faresti con il primo? Nella misura in cui uno è interessato a quest'ultimo, penso che potrebbe essere gestito facendo implementare entrambi i tipi IReadableHolder<T>.
supercat

@supercat sto cercando di indovinare che avrebbero dovuto implementare IMappable<K<_>, T>il metodo K<S> Map(Func<T, S> f), di esecuzione in quanto IMappable<Option<_>, T>, IMappable<List<_>, T>. Quindi dovresti costringerti K<T> : IMappable<K<_>, T>a farne un uso.
GregRos,

1
Il casting non è un'analogia appropriata. Ciò che viene fatto con le classi di tipi (o costrutti simili) è che mostri come un determinato tipo soddisfa il tipo più elevato. Questo di solito implica la definizione di nuove funzioni o la visualizzazione di quali funzioni esistenti (o metodi se è un linguaggio OO come Scala) che possono essere utilizzati. Una volta fatto ciò, tutte le funzioni definite per funzionare con il tipo superiore funzioneranno tutte con quel tipo. Ma è molto più di un'interfaccia perché sono definiti più di un semplice set di firme di funzioni / metodi. Immagino che dovrò andare a scrivere una risposta per mostrare come funziona.
itsbruce

Risposte:


5

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 Tscelta. 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) mpche accetta un parametro di tipo e richiede che sia associato alla funzione mapche trasforma un mp<a>in mp<b>. Possiamo quindi scrivere funzioni che vincolano tipi di tipo più elevato, Mappableproprio 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.


Sono stato troppo impegnato a cambiare lavoro, ma finalmente troverò un po 'di tempo per scrivere la mia risposta. Penso che tu sia stato distratto dall'idea dei vincoli, però. Un aspetto significativo dei tipi di tipo superiore è che consentono di definire funzioni / comportamenti che possono essere applicati a qualsiasi tipo che può essere logicamente dimostrato di qualificarsi. Lungi dall'imporre vincoli ai tipi esistenti, le classi di tipi (un'applicazione di tipi di tipo superiore) aggiungono loro un comportamento.
itsbruce,

Penso che sia un problema di semantica. Una classe di tipo definisce una classe di tipi. Quando si definisce una funzione come (Mappable mp) => mp a -> mp b, è stato imposto un vincolo mpper essere un membro della classe type Mappable. Quando si dichiara un tipo tale da Optionessere un'istanza di Mappable, si aggiunge il comportamento a quel tipo. Immagino che potresti sfruttare quel comportamento localmente senza mai vincolare alcun tipo, ma non è diverso dalla definizione di una normale funzione.
GregRos,

Inoltre, sono abbastanza certo che le classi di tipi non sono direttamente correlate a tipi di tipo superiore. Scala ha tipi di tipo superiore, ma non classi di tipo, e puoi limitare le classi di tipo ai tipi con il tipo *senza renderli inutilizzabili. È sicuramente vero che le classi di tipi sono molto potenti quando si lavora con tipi di tipo più elevato.
GregRos,

Scala ha classi di tipi. Sono implementati con impliciti. Gli impliciti forniscono l'equivalente delle istanze della classe di tipo Haskell. È un'implementazione più fragile di quella di Haskell perché non esiste una sintassi dedicata. Ma è sempre stato uno degli scopi chiave degli Scala impliciti e fanno il loro lavoro.
itsbruce
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.