Я думаю, що є сенс пояснювати екзистенційні типи разом із універсальними типами, оскільки ці два поняття є взаємодоповнюючими, тобто одне є «протилежним» іншому.
Я не можу відповісти на кожну деталь про екзистенційні типи (наприклад, дати точне визначення, перерахувати всі можливі варіанти використання, їхнє відношення до абстрактних типів даних тощо), тому що я просто недостатньо обізнаний для цього. Я продемонструю лише (використовуючи 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<α>
який представляє вузол у двійковому дереві. Кожен вузол зберігає a value
певного типу α і має посилання на необов'язкові left
та right
підряди одного типу.
функція, height
яка повертає найбільшу відстань від будь-якого вузла листа до кореневого вузла t
.
Тепер перейдемо до вищевказаного псевдокоду height
правильного синтаксису Java! (Я продовжую опускати деяку котельну плиту заради стислості, як-от модифікатори орієнтації на об'єкти та доступність.) Я збираюся показати два можливі рішення.
1. Рішення універсального типу:
Найбільш очевидний виправлення - просто зробити height
загальний, ввівши параметр типу α у свій підпис:
<α> int height(Tree<α> t)
{
return (t != null) ? 1 + max( height(t.left), height(t.right) )
: 0;
}
Це дозволить вам оголосити змінні та створити вирази типу α всередині цієї функції, якщо ви цього хочете. Але ...
2. Екзистенціальний тип розчину:
Якщо ви подивитесь на тіло нашого методу, ви помітите, що ми фактично не отримуємо доступу до будь-якого типу α або не працюємо з ним ! Немає виразів такого типу, а також ніяких змінних, оголошених із цим типом ... так, чому ми взагалі повинні робити height
загальні? Чому ми не можемо просто забути про α ? Як виявилося, ми можемо:
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 |
=====================+=====================================