Je pense qu'il est logique d'expliquer les types existentiels avec les types universels, puisque les deux concepts sont complémentaires, c'est-à-dire que l'un est «l'opposé» de l'autre.
Je ne peux pas répondre à tous les détails sur les types existentiels (comme donner une définition exacte, lister toutes les utilisations possibles, leur relation avec les types de données abstraits, etc.) parce que je ne suis tout simplement pas assez informé pour cela. Je vais démontrer seulement (en utilisant Java) ce que cet article HaskellWiki déclare être le principal effet des types existentiels:
Les types existentiels peuvent être utilisés à plusieurs fins différentes. Mais ce qu'ils font est de «cacher» une variable de type sur le côté droit. Normalement, toute variable de type apparaissant à droite doit également apparaître à gauche […]
Exemple de configuration:
Le pseudo-code suivant n'est pas tout à fait valide Java, même s'il serait assez facile de résoudre ce problème. 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 vous l'expliquer brièvement. Nous définissons…
un type récursif Tree<α>
qui représente un nœud dans un arbre binaire. Chaque nœud stocke a d'un value
certain type α et a des références à des sous left
- right
arborescences optionnelles et du même type.
une fonction height
qui renvoie la distance la plus éloignée de tout nœud feuille au nœud racine t
.
Maintenant, transformons le pseudo-code ci-dessus pour height
en une syntaxe Java appropriée! (Je continuerai à omettre certaines règles standard pour des raisons de brièveté, telles que l'orientation des objets et les modificateurs d'accessibilité.) Je vais montrer deux solutions possibles.
1. Solution de type universel:
La solution la plus évidente consiste simplement à rendre 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 permettrait de déclarer des variables et de créer des expressions de type α à l' intérieur de cette fonction, si vous le souhaitez. Mais...
2. Solution de type existentiel:
Si vous regardez le corps de notre méthode, vous remarquerez que nous n'accédons pas ou ne travaillons pas avec quoi que ce soit de type α ! Il n'y a aucune expression ayant ce type, ni aucune variable déclarée avec ce type ... alors, pourquoi devons-nous rendre height
générique du tout? Pourquoi ne pouvons-nous pas simplement oublier α ? Il s'avère que nous pouvons:
int height(Tree<?> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Comme je l'ai écrit au tout début de cette réponse, les types existentiels et universels sont de nature complémentaire / double. Ainsi, si la solution de type universel devait rendre height
plus générique, alors nous devrions nous attendre à ce que les types existentiels aient l'effet inverse: la rendre moins générique, à savoir en masquant / supprimant le paramètre de type α .
Par conséquent, vous ne pouvez plus faire référence au type de t.value
dans cette méthode ni manipuler aucune expression de ce type, car aucun identificateur ne lui est lié. (Le ?
joker est un jeton spécial, pas un identifiant qui "capture" un type.) t.value
Est effectivement devenu opaque; peut-être que la seule chose que vous pouvez encore faire est de le convertir en caractères 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 |
=====================+=====================================