it-swarm.com.ru

Что такое экзистенциальный тип?

Я прочитал статью в Википедии Экзистенциальные типы. Я понял, что они называются экзистенциальными типами из-за экзистенциального оператора (∃). Я не уверен, какой в ​​этом смысл. В чем разница между

T = ∃X { X a; int f(X); }

а также

T = ∀x { X a; int f(X); }

?

157
Claudiu

Когда кто-то определяет универсальный тип ∀X, они говорят: Вы можете подключить любой тип, какой хотите, мне не нужно ничего знать о типе, чтобы выполнять свою работу, я буду только называйте его непрозрачным как X.

Когда кто-то определяет экзистенциальный тип ∃X, он говорит: Я буду использовать любой тип, который мне здесь нужен; вы ничего не знаете о типе, поэтому вы можете ссылаться на него только как X.

Универсальные типы позволяют писать такие вещи, как:

void copy<T>(List<T> source, List<T> dest) {
   ...
}

Функция copy не имеет представления о том, каким на самом деле будет T, но это не нужно.

Экзистенциальные типы позволят вам писать такие вещи, как:

interface VirtualMachine<B> {
   B compile(String source);
   void run(B bytecode);
}

// Now, if you had a list of VMs you wanted to run on the same input:
void runAllCompilers(List<∃B:VirtualMachine<B>> vms, String source) {
   for (∃B:VirtualMachine<B> vm : vms) {
      B bytecode = vm.compile(source);
      vm.run(bytecode);
   }
}

Каждая реализация виртуальной машины в списке может иметь свой тип байт-кода. Функция runAllCompilers не имеет представления о типе байт-кода, но в этом нет необходимости; все, что он делает - это передает байт-код с VirtualMachine.compile на VirtualMachine.run.

Подстановочные знаки типов Java (например: List<?>) являются очень ограниченной формой экзистенциальных типов.

Обновление: Забыл упомянуть, что вы можете сортировать экзистенциальные типы с помощью универсальных типов. Сначала оберните ваш универсальный тип, чтобы скрыть параметр типа. Во-вторых, инвертированный контроль (это эффективно меняет часть "ты" и "я" в вышеприведенных определениях, что является основным отличием экзистенциалов и универсалий).

// A wrapper that hides the type parameter 'B'
interface VMWrapper {
   void unwrap(VMHandler handler);
}

// A callback (control inversion)
interface VMHandler {
   <B> void handle(VirtualMachine<B> vm);
}

Теперь мы можем иметь вызов VMWrapper нашей собственной VMHandler, которая имеет универсально типизированную функцию handle. Чистый эффект тот же, наш код должен обрабатывать B как непрозрачный.

void runWithAll(List<VMWrapper> vms, final String input)
{
   for (VMWrapper vm : vms) {
      vm.unwrap(new VMHandler() {
         public <B> void handle(VirtualMachine<B> vm) {
            B bytecode = vm.compile(input);
            vm.run(bytecode);
         }
      });
   }
}

Пример реализации VM:

class MyVM implements VirtualMachine<byte[]>, VMWrapper {
   public byte[] compile(String input) {
      return null; // TODO: somehow compile the input
   }
   public void run(byte[] bytecode) {
      // TODO: Somehow evaluate 'bytecode'
   }
   public void unwrap(VMHandler handler) {
      handler.handle(this);
   }
}
170
Kannan Goundan

Значение экзистенциального типа , такого как ∃x. F(x), представляет собой пару , содержащую некоторые тип x и значение типа F(x). Принимая во внимание, что значение полиморфного типа, такого как ∀x. F(x), является функцией , которая принимает некоторый тип x, а создает значение типа F(x). В обоих случаях тип закрывается над каким-то конструктором типа F.

Обратите внимание, что в этом представлении смешиваются типы и значения. Существующее доказательство - один тип и одно значение. Универсальное доказательство - это целое семейство значений, проиндексированных по типу (или отображение типов в значения).

Таким образом, разница между двумя указанными вами типами заключается в следующем:

T = ∃X { X a; int f(X); }

Это означает: значение типа T содержит тип с именем X, значение a:X и функцию f:X->int. Производитель значений типа T получает возможность выбрать любой тип для X, а потребитель не может ничего знать о X. За исключением того, что есть один пример этого, называемый a, и что это значение можно превратить в int, передав его f. Другими словами, значение типа T знает, как каким-то образом создать int. Ну, мы могли бы исключить промежуточный тип X и просто сказать:

T = int

Универсально выраженный немного отличается.

T = ∀X { X a; int f(X); }

Это означает: значение типа T может быть присвоено любому типу X, и оно будет выдавать значение a:X и функцию f:X->int независимо от того, что X. Другими словами: потребитель значений типа T может выбрать любой тип для X. И производитель значений типа T вообще ничего не может знать о X, но он должен иметь возможность генерировать значение a для любого выбора X и иметь возможность превратить такое значение в int.

Очевидно, что реализация этого типа невозможна, потому что нет программы, которая могла бы создать значение каждого мыслимого типа. Если вы не допустите нелепости, такие как null или дно.

Поскольку экзистенциальный является парой, экзистенциальный аргумент может быть преобразован в универсальный с помощью каррирования .

(∃b. F(b)) -> Int

такой же как:

∀b. (F(b) -> Int)

Первый является экзистенциальным ранга 2 . Это приводит к следующему полезному свойству:

Каждый экзистенциально квантифицированный тип ранга n+1 является универсально квантифицированным типом ранга n.

Существует стандартный алгоритм преобразования экзистенциалов в универсалии, называемый Skolemization .

96
Apocalisp

Я думаю, что имеет смысл объяснять экзистенциальные типы вместе с универсальными типами, поскольку эти два понятия дополняют друг друга, то есть одно является "противоположностью" другого.

Я не могу ответить на каждую деталь об экзистенциальных типах (таких как точное определение, список всех возможных применений, их связь с абстрактными типами данных и т.д.), Потому что я просто недостаточно осведомлен для этого. Я продемонстрирую только (с использованием 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 универсальным, введя параметр типа α в его сигнатуру:

<α> 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   |                  
=====================+=====================================
30
stakx

Все это хорошие примеры, но я решил ответить на это немного по-другому. Напомним из математики, что ∀x. P(x) означает "для всех x, я могу доказать, что P (x)". Другими словами, это своего рода функция, вы даете мне крестик, и у меня есть метод, чтобы доказать это для вас.

В теории типов мы говорим не о доказательствах, а о типах. Таким образом, в этом пространстве мы имеем в виду "для любого типа X, который вы мне дадите, я дам вам конкретный тип P". Теперь, поскольку мы не даем P много информации о X, кроме того факта, что это тип, P не может ничего с этим поделать, но есть несколько примеров. P может создать тип "все пары одного типа": P<X> = Pair<X, X> = (X, X). Или мы можем создать тип параметра: P<X> = Option<X> = X | Nil, где Nil - это тип нулевых указателей. Из него можно составить список: List<X> = (X, List<X>) | Nil. Обратите внимание, что последний является рекурсивным, значения List<X> либо являются парами, где первый элемент является X, а вторым элементом является List<X>, либо это нулевой указатель.

Теперь в математике ∃x. P(x) означает "я могу доказать, что существует определенный x такой, что P(x) истинно"). Таких иксов может быть много, но для доказательства достаточно одного. Другой способ думать об этом состоит в том, что должен существовать непустой набор пар доказательство-доказательство {(x, P (x))}.

Переведено в теорию типов: тип в семействе ∃X.P<X> является типом X и соответствующим типом P<X>. Обратите внимание на то, что до того, как мы дали X X P (так что мы знали о X все, но P очень мало), теперь все наоборот. P<X> не обещает давать какую-либо информацию о X, только то, что там есть один, и что это действительно тип.

Чем это полезно? Ну, P может быть типом, который имеет способ раскрытия своего внутреннего типа X. Примером может быть объект, который скрывает внутреннее представление своего состояния X. Хотя у нас нет никакого способа непосредственно манипулировать им, мы можем наблюдать его эффект, poking at P. Может быть много реализаций этого типа, но вы можете использовать все эти типы независимо от того, какой из них был выбран.

14
Rogon

Экзистенциальный тип - это непрозрачный тип.

Подумайте о дескрипторе файла в Unix. Вы знаете, что его тип int, поэтому вы можете легко подделать его. Например, вы можете попробовать прочитать из дескриптора 43. Если так получилось, что у программы есть файл, открытый с помощью этого конкретного дескриптора, вы будете читать из него. Ваш код не должен быть вредоносным, просто небрежным (например, дескриптор может быть неинициализированной переменной).

Экзистенциальный тип скрыт от вашей программы. Если fopen вернул экзистенциальный тип, все, что вы можете с ним сделать - это использовать его с некоторыми библиотечными функциями, которые принимают этот экзистенциальный тип. Например, следующий псевдокод будет компилироваться:

let exfile = fopen("foo.txt"); // No type for exfile!
read(exfile, buf, size);

Интерфейс "чтение" объявлен как:

Существует тип T такой, что:

size_t read(T exfile, char* buf, size_t size);

Переменная exfile - это не int, не char*, не struct File - ничего, что вы можете выразить в системе типов. Вы не можете объявить переменную, тип которой неизвестен, и вы не можете привести, скажем, указатель к этому неизвестному типу. Язык не позволит вам.

12
Bartosz Milewski

Чтобы прямо ответить на ваш вопрос:

В случае универсального типа использование T должно включать параметр типа X. Например, T<String> или T<Integer>. Для экзистенциального типа использование T не включает этот параметр типа, поскольку он неизвестен или не имеет значения - просто используйте T (или в Java T<?>).

Дальнейшая информация:

Универсальные/абстрактные типы и экзистенциальные типы представляют собой двойственную перспективу между потребителем/клиентом объекта/функции и производителем/реализацией этого. Когда одна сторона видит универсальный тип, другая видит экзистенциальный тип.

В Java вы можете определить универсальный класс:

public class MyClass<T> {
   // T is existential in here
   T whatever; 
   public MyClass(T w) { this.whatever = w; }

   public static MyClass<?> secretMessage() { return new MyClass("bazzlebleeb"); }
}

// T is universal from out here
MyClass<String> mc1 = new MyClass("foo");
MyClass<Integer> mc2 = new MyClass(123);
MyClass<?> mc3 = MyClass.secretMessage();
  • С точки зрения clientMyClass, T универсальна, потому что вы можете заменить любой тип на T при использовании этого класса, и вы должны знать фактический тип T всякий раз, когда вы используете экземпляр MyClass
  • С точки зрения методов экземпляра в самом MyClass, T является экзистенциальным, потому что он не знает реального типа T
  • В Java ? представляет экзистенциальный тип - таким образом, когда вы находитесь внутри класса, T в основном ?. Если вы хотите обработать экземпляр MyClass с T экзистенциальным, вы можете объявить MyClass<?> как в примере secretMessage() выше.

Экзистенциальные типы иногда используются, чтобы скрыть детали реализации чего-либо, как обсуждалось в другом месте. Версия Java может выглядеть так:

public class ToDraw<T> {
    T obj;
    Function<Pair<T,Graphics>, Void> draw;
    ToDraw(T obj, Function<Pair<T,Graphics>, Void>
    static void draw(ToDraw<?> d, Graphics g) { d.draw.apply(new Pair(d.obj, g)); }
}

// Now you can put these in a list and draw them like so:
List<ToDraw<?>> drawList = ... ;
for(td in drawList) ToDraw.draw(td);

Немного сложно понять это правильно, потому что я притворяюсь, что я использую какой-то функциональный язык программирования, а Java нет. Но дело здесь в том, что вы фиксируете какое-то состояние и список функций, которые работают с этим состоянием, и вы не знаете реальный тип части состояния, но функции знают, так как они уже сопоставлены с этим типом. ,.

Теперь в Java все не финальные не примитивные типы являются частично экзистенциальными. Это может показаться странным, но поскольку переменная, объявленная как Object, потенциально может быть подклассом Object, вместо этого вы не можете объявить определенный тип, только "этот тип или подкласс". Итак, объекты представлены как бит состояния, а также список функций, которые работают в этом состоянии - какая именно функция вызывается, определяется во время выполнения поиском. Это очень похоже на использование экзистенциальных типов выше, где у вас есть часть экзистенциального состояния и функция, которая работает с этим состоянием.

В статически типизированных языках программирования без подтипов и приведений экзистенциальные типы позволяют управлять списками объектов различного типа. Список T<Int> не может содержать T<Long>. Однако список T<?> может содержать любой вариант T, что позволяет помещать в список множество различных типов данных и преобразовывать их все в целое число (или выполнять любые операции, которые предоставляются внутри структуры данных) по требованию.

Почти всегда можно конвертировать запись с экзистенциальным типом в запись без использования замыканий. Закрытие также типизировано экзистенциально, так как свободные переменные, по которым оно закрыто, скрыты от вызывающей стороны. Таким образом, язык, который поддерживает замыкания, но не экзистенциальные типы, может позволить вам создавать замыкания, которые имеют то же скрытое состояние, которое вы бы поместили в экзистенциальную часть объекта.

11
Dobes Vandermeer

Кажется, я немного опаздываю, но в любом случае этот документ добавляет другое представление о том, что такое экзистенциальные типы, хотя они не являются специфически независимыми от языка, тогда должно быть довольно легко понять экзистенциальные типы: http: // www .cs.uu.nl/groups/ST/Projects/ehc/ehc-book.pdf (глава 8)

Различие между универсально и экзистенциально определенным типом может быть охарактеризовано следующим наблюдением:

  • Использование значения с квантифицированным типом определяет тип, который нужно выбрать для создания экземпляра квантифицированной переменной типа. Например, вызывающая функция идентификатора "id :: .a.a → a" определяет тип, который нужно выбрать для переменной типа a для этого конкретного применения id. Для приложения функции "id 3" этот тип равен Int.

  • Создание значения с квантифицированным типом определяет и скрывает тип переменной квантифицированного типа. Например, создатель ".a. (A, a → Int)" мог построить значение этого типа из "(3, λx → x)"; другой создатель создал значение с таким же типом из "(’ x ’, λx → ord x)". С точки зрения пользователя оба значения имеют одинаковый тип и, таким образом, являются взаимозаменяемыми. У значения есть определенный тип, выбранный для переменной типа a, но мы не знаем, какой тип, поэтому эта информация больше не может быть использована. Эта информация о типе конкретного значения была "забыта"; мы только знаем, что это существует.

6
themarketka

Универсальный тип существует для всех значений параметра (ов) типа. Экзистенциальный тип существует только для значений параметра (ов) типа, которые удовлетворяют ограничениям экзистенциального типа.

Например, в Scala одним из способов выражения экзистенциального типа является абстрактный тип, который ограничен некоторыми верхними или нижними границами.

trait Existential {
  type Parameter <: Interface
}

Эквивалентно ограниченный универсальный тип является экзистенциальным типом, как в следующем примере.

trait Existential[Parameter <: Interface]

Любой используемый сайт может использовать Interface, потому что любые инстанцируемые подтипы Existential должны определять type Parameter, который должен реализовывать Interface.

вырожденный случай экзистенциального типа в Scala является абстрактным типом, на который никогда не ссылаются, и, следовательно, его не нужно определять каким-либо подтип. Это эффективно имеет сокращенную запись List[_]в Scala и List<?> в Java.

Мой ответ был вдохновлен предложением Мартина Одерского объединить абстрактные и экзистенциальные типы. сопровождающий слайд помогает понять.

4
Shelby Moore III

Исследования абстрактных типов данных и сокрытия информации привели экзистенциальные типы в языки программирования. Создание абстрактного типа данных скрывает информацию об этом типе, поэтому клиент этого типа не может злоупотреблять им. Скажем, у вас есть ссылка на объект ... некоторые языки позволяют вам преобразовывать эту ссылку в ссылку на байты и делать все, что вы хотите, с этим фрагментом памяти. В целях гарантии поведения программы для языка полезно обеспечить, чтобы вы действовали только в отношении ссылки на объект с помощью методов, предоставляемых конструктором объекта. Вы знаете, что тип существует, но не более того.

Увидеть:

Абстрактные типы имеют экзистенциальный тип, MITCHEL & PLOTKIN

http://theory.stanford.edu/~jcm/papers/mitch-plotkin-88.pdf

3
ja.

Я создал эту диаграмму. Я не знаю, строго ли это. Но если это поможет, я рад. enter image description here

1
v.oddou