Myślę, że sensowne jest wyjaśnianie typów egzystencjalnych razem z typami uniwersalnymi, ponieważ te dwa pojęcia są komplementarne, tj. Jedno jest „przeciwieństwem” drugiego.
Nie mogę odpowiedzieć na wszystkie szczegóły dotyczące typów egzystencjalnych (takich jak podanie dokładnej definicji, lista wszystkich możliwych zastosowań, ich związek z abstrakcyjnymi typami danych itp.), Ponieważ po prostu nie mam do tego wystarczającej wiedzy. Pokażę tylko (używając Java), co ten artykuł HaskellWiki określa jako główny efekt typów egzystencjalnych:
Typy egzystencjalne mogą być używane do kilku różnych celów. Ale to, co robią, to „ukrywanie” zmiennej typu po prawej stronie. Zwykle każda zmienna typu pojawiająca się po prawej stronie musi również pojawić się po lewej stronie […]
Przykładowa konfiguracja:
Poniższy pseudo-kod nie jest całkiem poprawnym kodem Java, chociaż byłoby to łatwe do naprawienia. Właściwie to właśnie zamierzam zrobić w tej odpowiedzi!
class Tree<α>
{
α value;
Tree<α> left;
Tree<α> right;
}
int height(Tree<α> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Pozwól, że ci to krótko przeliteruję. Definiujemy…
typ rekurencyjny, Tree<α>
który reprezentuje węzeł w drzewie binarnym. Każdy węzeł przechowuje value
jakiś typ α i ma odniesienia do opcjonalnych left
i right
poddrzew tego samego typu.
funkcja, height
która zwraca najdalszą odległość od dowolnego węzła liścia do węzła głównego t
.
Teraz zamieńmy powyższy pseudokod height
na właściwą składnię Java! (Ze względu na zwięzłość będę nadal pomijał pewne schematy, takie jak orientacja obiektowa i modyfikatory dostępności). Pokażę dwa możliwe rozwiązania.
1. Rozwiązanie uniwersalne:
Najbardziej oczywistą poprawką jest po prostu utworzenie height
generycznego poprzez wprowadzenie parametru typu α do jego podpisu:
<α> int height(Tree<α> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Umożliwiłoby to deklarowanie zmiennych i tworzenie wyrażeń typu α wewnątrz tej funkcji, gdybyś chciał. Ale...
2. Rozwiązanie typu egzystencjalnego:
Jeśli spojrzysz na treść naszej metody, zauważysz, że tak naprawdę nie mamy dostępu do niczego typu α ani nie pracujemy z nim ! Nie ma wyrażeń posiadających ten typ, ani żadnych zmiennych zadeklarowanych z tym typem ... więc dlaczego w ogóle musimy tworzyć height
rodzajowe? Dlaczego nie możemy po prostu zapomnieć o α ? Jak się okazuje, możemy:
int height(Tree<?> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Jak napisałem na samym początku tej odpowiedzi, typy egzystencjalne i uniwersalne mają charakter komplementarny / dualny. Tak więc, gdyby uniwersalne rozwiązanie typu miało uczynić height
bardziej rodzajowym, to należałoby się spodziewać, że typy egzystencjalne mają odwrotny skutek: uczynienie go mniej rodzajowym, a mianowicie poprzez ukrycie / usunięcie parametru typu α .
W konsekwencji nie można już odwoływać się do typu t.value
w tej metodzie ani manipulować jakimikolwiek wyrażeniami tego typu, ponieważ żaden identyfikator nie został z nim powiązany. (Symbol ?
wieloznaczny jest specjalnym tokenem, a nie identyfikatorem, który „przechwytuje” typ.) W t.value
rzeczywistości stał się nieprzejrzysty; być może jedyne, co nadal możesz z nim zrobić, to przerzucić go na typ Object
.
Podsumowanie:
===========================================================
| 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 |
=====================+=====================================