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