Creo que tiene sentido explicar los tipos existenciales junto con los tipos universales, ya que los dos conceptos son complementarios, es decir, uno es el "opuesto" del otro.
No puedo responder a cada detalle sobre los tipos existenciales (como dar una definición exacta, enumerar todos los usos posibles, su relación con los tipos de datos abstractos, etc.) porque simplemente no tengo el conocimiento suficiente para eso. Solo demostraré (usando Java) lo que este artículo de HaskellWiki dice que es el efecto principal de los tipos existenciales:
Los tipos existenciales se pueden usar para varios propósitos diferentes. Pero lo que hacen es 'ocultar' una variable de tipo en el lado derecho. Normalmente, cualquier variable de tipo que aparezca a la derecha también debe aparecer a la izquierda […]
Ejemplo de configuración:
El siguiente pseudocódigo no es Java válido, a pesar de que sería lo suficientemente fácil de solucionar. De hecho, ¡eso es exactamente lo que voy a hacer en esta respuesta!
class Tree<α>
{
α value;
Tree<α> left;
Tree<α> right;
}
int height(Tree<α> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Déjame explicarte esto brevemente. Estamos definiendo ...
Un tipo recursivo Tree<α>que representa un nodo en un árbol binario. Cada nodo almacena un valuetipo de α y tiene referencias a subtítulos opcionales lefty rightdel mismo tipo.
una función heightque devuelve la distancia más lejana desde cualquier nodo hoja al nodo raíz t.
¡Ahora, cambiemos el pseudocódigo anterior heighta la sintaxis Java correcta! (Seguiré omitiendo algunas repeticiones por razones de brevedad, como la orientación a objetos y los modificadores de accesibilidad). Voy a mostrar dos posibles soluciones.
1. Solución de tipo universal:
La solución más obvia es simplemente hacer heightgenérico introduciendo el parámetro de tipo α en su firma:
<α> int height(Tree<α> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Esto le permitiría declarar variables y crear expresiones de tipo α dentro de esa función, si así lo desea. Pero...
2. Solución de tipo existencial:
Si observa el cuerpo de nuestro método, notará que en realidad no estamos accediendo o trabajando con nada del tipo α . No hay expresiones que tengan ese tipo, ni ninguna variable declarada con ese tipo ... entonces, ¿por qué tenemos que hacer algo heightgenérico? ¿Por qué no podemos simplemente olvidarnos de α ? Como resultado, podemos:
int height(Tree<?> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Como escribí al comienzo de esta respuesta, los tipos existenciales y universales son de naturaleza complementaria / dual. Por lo tanto, si la solución de tipo universal fuera hacer height más genérico, entonces deberíamos esperar que los tipos existenciales tengan el efecto contrario: hacerlo menos genérico, es decir, al ocultar / eliminar el parámetro de tipo α .
Como consecuencia, ya no puede hacer referencia al tipo de t.valueen este método ni manipular ninguna expresión de ese tipo, porque no se ha vinculado ningún identificador. (El ?comodín es un token especial, no un identificador que "captura" un tipo). De hecho, se t.valueha vuelto opaco; tal vez lo único que aún puede hacer con él es moldearlo Object.
Resumen:
===========================================================
| 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 |
=====================+=====================================