Я думаю, что имеет смысл объяснять экзистенциальные типы вместе с универсальными типами, поскольку эти два понятия дополняют друг друга, то есть одно является «противоположностью» другого.
Я не могу ответить на каждую деталь об экзистенциальных типах (например, дать точное определение, перечислить все возможные варианты использования, их связь с абстрактными типами данных и т. Д.), Потому что я просто недостаточно осведомлен для этого. Я продемонстрирую только (с использованием Java), что в этой статье на HaskellWiki говорится, что это основной эффект экзистенциальных типов:
Экзистенциальные типы могут использоваться для нескольких различных целей. Но то, что они делают, это «скрывают» переменную типа с правой стороны. Обычно любая переменная типа, появляющаяся справа, также должна появляться слева […]
Пример установки:
Следующий псевдокод не совсем корректный Java, хотя это было бы достаточно легко исправить. На самом деле, это именно то, что я собираюсь сделать в этом ответе!
class Tree<α>
{
α value;
Tree<α> left;
Tree<α> right;
}
int height(Tree<α> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Позвольте мне кратко изложить это для вас. Мы определяем ...
рекурсивный тип, Tree<α>представляющий узел в двоичном дереве. Каждый узел хранит valueнекоторый тип α и имеет ссылки на необязательныеleft и rightподдеревья того же типа.
функция height которая возвращает самое дальнее расстояние от любого конечного узла до корневого узла t.
Теперь давайте превратим приведенный выше псевдокод heightв правильный синтаксис Java! (Я буду продолжать опускать некоторые шаблоны для краткости, такие как модификаторы объектной ориентации и доступности.) Я собираюсь показать два возможных решения.
1. Универсальное типовое решение:
Наиболее очевидное исправление состоит в том, чтобы просто сделать heightgeneric, введя параметр типа α в его сигнатуру:
<α> int height(Tree<α> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Это позволит вам объявлять переменные и создавать выражения типа α внутри этой функции, если вы захотите. Но...
2. Экзистенциальное решение типа:
Если вы посмотрите на тело нашего метода, вы заметите, что мы на самом деле не обращаемся и не работаем с чем-либо типа α ! Нет ни одного выражения с таким типом, ни переменных, объявленных с этим типом ... итак, почему мы вообще должны делать heightgeneric? Почему мы не можем просто забыть об α ? Как оказалось, мы можем:
int height(Tree<?> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Как я уже писал в самом начале этого ответа, экзистенциальные и универсальные типы имеют комплементарный / двойственный характер. Таким образом, если решение универсального типа должно было сделать height более универсальным, то следует ожидать, что экзистенциальные типы будут иметь противоположный эффект: сделать его менее универсальным, а именно скрыть / удалить параметр типа α .
Как следствие, вы больше не можете ссылаться на тип t.valueв этом методе и манипулировать какими-либо выражениями этого типа, потому что с ним не связан ни один идентификатор. ( ?Подстановочный знак - это специальный токен, а не идентификатор, который «захватывает» тип.) t.valueФактически стал непрозрачным; пожалуй, единственное, что вы все еще можете сделать с ним, это набрать егоObject .
Резюме:
===========================================================
| 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 |
=====================+=====================================