Penso che abbia senso spiegare i tipi esistenziali insieme ai tipi universali, poiché i due concetti sono complementari, ovvero uno è "l'opposto" dell'altro.
Non posso rispondere a tutti i dettagli sui tipi esistenziali (come dare una definizione esatta, elencare tutti gli usi possibili, la loro relazione con tipi di dati astratti, ecc.) Perché semplicemente non sono abbastanza informato per quello. Dimostrerò solo (usando Java) ciò che questo articolo di HaskellWiki afferma essere l'effetto principale dei tipi esistenziali:
I tipi esistenziali possono essere utilizzati per diversi scopi. Ma quello che fanno è "nascondere" una variabile di tipo sul lato destro. Normalmente, qualsiasi variabile di tipo che appare a destra deve apparire anche a sinistra […]
Esempio di installazione:
Il seguente pseudo-codice non è del tutto valido Java, anche se sarebbe abbastanza facile risolverlo. In effetti, è esattamente quello che ho intenzione di fare in questa risposta!
class Tree<α>
{
α value;
Tree<α> left;
Tree<α> right;
}
int height(Tree<α> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Lascia che ti spieghi brevemente questo per te. Stiamo definendo ...
un tipo ricorsivo Tree<α>
che rappresenta un nodo in un albero binario. Ogni nodo memorizza un value
tipo di α e ha riferimenti a facoltativi left
e right
sottotitoli dello stesso tipo.
una funzione height
che restituisce la distanza più lontana da qualsiasi nodo foglia al nodo radice t
.
Ora trasformiamo lo pseudo-codice sopra riportato height
in una corretta sintassi Java! (Continuerò a omettere un po 'di scaldabagno per brevità, come l'orientamento agli oggetti e i modificatori dell'accessibilità.) Mostrerò due possibili soluzioni.
1. Soluzione di tipo universale:
La soluzione più ovvia è semplicemente quella di rendere height
generico introducendo il parametro type α nella sua firma:
<α> int height(Tree<α> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Ciò ti consentirebbe di dichiarare variabili e creare espressioni di tipo α all'interno di quella funzione, se lo desideri. Ma...
2. Soluzione di tipo esistenziale:
Se osservi il corpo del nostro metodo, noterai che non stiamo effettivamente accedendo o lavorando con nulla del tipo α ! Non ci sono espressioni che hanno quel tipo, né alcuna variabile dichiarata con quel tipo ... quindi, perché dobbiamo fare del tutto height
generico? Perché non possiamo semplicemente dimenticare α ? A quanto pare, possiamo:
int height(Tree<?> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Come ho scritto all'inizio di questa risposta, i tipi esistenziali e universali sono di natura complementare / doppia. Pertanto, se la soluzione di tipo universale dovesse rendere height
più generica, dovremmo aspettarci che i tipi esistenziali abbiano l'effetto opposto: renderla meno generica, ovvero nascondendo / rimuovendo il parametro di tipo α .
Di conseguenza, non è più possibile fare riferimento al tipo di t.value
in questo metodo né manipolare espressioni di quel tipo, poiché nessun identificatore è stato associato ad esso. (Il ?
carattere jolly è un token speciale, non un identificatore che "cattura" un tipo.) t.value
È diventato effettivamente opaco; forse l'unica cosa che puoi ancora fare con esso è scrivere il tipo Object
.
Sommario:
===========================================================
| universally existentially
| quantified type quantified type
---------------------+-------------------------------------
calling method |
needs to know | yes no
the type argument |
---------------------+-------------------------------------
called method |
can use / refer to | yes no
the type argument |
=====================+=====================================