Je pense qu'il fait sens pour expliquer existentielle types avec universels, car les deux concepts sont complémentaires, c'est à dire l'un est la "face" de l'autre.
Je ne peux pas répondre à tous les détails à propos existentielle types (tels que de donner une définition exacte, la liste de toutes les utilisations possibles, leur relation avec les types de données abstraites, etc.) parce que je suis tout simplement pas suffisamment de connaissance pour que. Je vais démontrer (en utilisant Java) que ce HaskellWiki article les états à être le principal effet de l'existentiel types:
Existentielle types peuvent être utilisés à plusieurs fins. Mais ce qu'ils font est de "cacher" une variable de type sur le côté droit. Normalement, n'importe quel type de variable apparaissant sur le droit doit également apparaître sur la gauche [...]
Exemple:
Le pseudo-code n'est pas tout à fait valable Java, même s'il serait assez facile à corriger. En fait, c'est exactement ce que je vais faire dans cette réponse!
class Tree<α>
{
α value;
Tree<α> left;
Tree<α> right;
}
int height(Tree<α> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Permettez-moi de sort pour vous. Nous sommes à la définition...
un type récursif Tree<α>
ce qui représente un nœud dans un arbre binaire. Chaque nœud stocke une value
d'un certain type α et a des références à optionnel left
et right
sous-arbres du même type.
une fonction height
qui renvoie le plus à distance de n'importe quel nœud feuille à la racine le nœud t
.
Maintenant, passons au-dessus de pseudo-code pour l' height
bonne syntaxe Java! (Je vais continuer à omettre certains réutilisable par souci de concision, tels que l'orientation de l'objet et de l'accessibilité des modificateurs.) Je vais vous montrer deux solutions possibles.
1. Universal type de solution:
La plus évidente solution est de simplement faire height
générique en introduisant le paramètre de type α dans sa signature:
<α> int height(Tree<α> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Cela vous permettra de déclarer des variables et créer des expressions de type α à l'intérieur de cette fonction, si vous vouliez. Mais...
2. Existentielle type de solution:
Si vous regardez notre méthode, vous remarquerez que nous ne sommes pas réellement accès à, ou de travailler avec, quoi que ce soit de type α! Il n'y a pas des expressions du type, ni toutes les variables déclarées avec ce type... alors, pourquoi avons-nous les faire height
générique? Pourquoi ne pouvons-nous pas tout simplement oublier α? Comme il s'avère, nous pouvons:
int height(Tree<?> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Comme je l'ai écrit au début de cette réponse, existentiels et universels sont complémentaires / double dans la nature. Ainsi, si le type universel solution était de faire de height
plus générique, alors nous devrions nous attendre que existentielle types ont l'effet inverse: c'est moins générique, à savoir en se cachant/retrait du type de paramètre α.
En conséquence, vous ne pouvez plus consulter le type d' t.value
dans cette méthode, ni manipuler des expressions de ce type, car aucun identifiant n'a été lié à elle. ( ?
Générique est un jeton spécial, pas un identificateur qui "capture" d'un type.) t.value
est effectivement devenu opaque; peut-être la seule chose que vous pouvez toujours faire avec il est de type jette - Object
.
Résumé:
===========================================================
| 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 |
=====================+=====================================