Я прочитал в Википедии статью Экзистенциальные типы . Я понял, что они называются экзистенциальными типами из-за экзистенциального оператора (∃). Я не уверен, какой в этом смысл. какая разница между
T = ∃X { X a; int f(X); }
и
T = ∀x { X a; int f(X); }
?
Ответы:
Когда кто-то определяет универсальный тип,
∀X
он говорит: вы можете подключить любой тип, какой хотите, мне не нужно ничего знать о типе, чтобы выполнять свою работу, я буду обозначать его только как непрозрачныйX
.Когда кто-то определяет экзистенциальный тип,
∃X
он говорит: я буду использовать любой тип, который мне нужен; Вы ничего не будете знать о типе, поэтому можете ссылаться на него только как непрозрачныйX
.Универсальные типы позволяют писать такие вещи, как:
copy
Функция не имеет ни малейшего представления , что наT
самом деле будет, но это не нужно.Экзистенциальные типы позволят вам писать такие вещи, как:
Каждая реализация виртуальной машины в списке может иметь свой тип байт-кода.
runAllCompilers
Функция не имеет ни малейшего представления о том , что тип байт - код, но это не нужно; все, что он делает, это передает байт-код изVirtualMachine.compile
вVirtualMachine.run
.Подстановочные знаки типов Java (ex:)
List<?>
являются очень ограниченной формой экзистенциальных типов.Обновление: забыл упомянуть, что вы можете сортировать экзистенциальные типы с помощью универсальных типов. Сначала оберните ваш универсальный тип, чтобы скрыть параметр типа. Во-вторых, инвертированный контроль (это эффективно меняет часть «ты» и «я» в вышеприведенных определениях, что является основным различием между экзистенциалами и универсалиями).
Теперь мы можем иметь собственный
VMWrapper
вызов,VMHandler
который имеет универсально типизированныйhandle
функцией . Чистый эффект тот же, наш код должен восприниматьсяB
как непрозрачный.Пример реализации ВМ:
источник
List<∃B:VirtualMachine<B>> vms
илиfor (∃B:VirtualMachine<B> vm : vms)
. (Поскольку это универсальные типы, разве вы не могли использовать?
подстановочные знаки Java вместо «самодельного» синтаксиса?) Я думаю, что это может помочь иметь пример кода, в котором нет универсальных типов, таких как∃B:VirtualMachine<B>
задействованные, а вместо этого «прямой»∃B
потому что универсальные типы легко ассоциируются с универсальными типами после ваших первых примеров кода.∃B
был явно о том, где происходит количественное определение. С подстановочным синтаксисом подразумевается квантификатор (List<List<?>>
фактически означает,∃T:List<List<T>>
а неList<∃T:List<T>>
). Кроме того, явное количественное определение позволяет вам ссылаться на тип (я изменил пример, чтобы воспользоваться этим, сохранив байт-код типаB
во временной переменной).Значение экзистенциального типа, такого
∃x. F(x)
как пара, содержит некоторый типx
и значение типаF(x)
. Принимая во внимание, что значение полиморфного типа, подобного,∀x. F(x)
является функцией, которая принимает некоторый типx
и производит значение типаF(x)
. В обоих случаях тип закрывается над каким-то конструктором типаF
.Обратите внимание, что в этом представлении смешиваются типы и значения. Существующее доказательство - один тип и одно значение. Универсальное доказательство - это целое семейство значений, проиндексированных по типу (или отображение типов в значения).
Таким образом, разница между двумя указанными вами типами заключается в следующем:
Это означает: значение типа
T
содержит вызываемый типX
, значениеa:X
и функциюf:X->int
. Производитель значений типаT
может выбрать любой тип,X
а потребитель ничего не может знатьX
. За исключением того, что есть один пример его вызоваa
и что это значение можно превратить в значениеint
, передав егоf
. Другими словами, значение типаT
знает, как произвести как-int
то. Ну, мы могли бы исключить промежуточный типX
и просто сказать:Универсально выраженный немного отличается.
Это означает: значение типа
T
может быть присвоено любому типуX
, и оно будет производить значениеa:X
и функциюf:X->int
независимо от того, чтоX
есть . Другими словами: потребитель значений типаT
может выбрать любой тип дляX
. И производитель значений типаT
вообще ничего не может знатьX
, но он должен быть в состоянии произвести значениеa
для любого выбораX
и быть в состоянии превратить такое значение вint
.Очевидно, что реализация этого типа невозможна, потому что нет программы, которая могла бы создать значение каждого мыслимого типа. Если только вы не допускаете нелепостей вроде
null
или низов.Поскольку экзистенциальный является парой, экзистенциальный аргумент может быть преобразован в универсальный с помощью карри .
такой же как:
Первый является экзистенциальным ранга 2 . Это приводит к следующему полезному свойству:
Существует стандартный алгоритм превращения экзистенциалов в универсалии, который называется сколемизация .
источник
Я думаю, что имеет смысл объяснять экзистенциальные типы вместе с универсальными типами, поскольку эти два понятия дополняют друг друга, то есть одно является «противоположностью» другого.
Я не могу ответить на каждую деталь об экзистенциальных типах (например, дать точное определение, перечислить все возможные варианты использования, их связь с абстрактными типами данных и т. Д.), Потому что я просто недостаточно осведомлен для этого. Я продемонстрирую только (с использованием Java), что в этой статье на HaskellWiki говорится, что это основной эффект экзистенциальных типов:
Пример установки:
Следующий псевдокод не совсем корректный Java, хотя это было бы достаточно легко исправить. На самом деле, это именно то, что я собираюсь сделать в этом ответе!
Позвольте мне кратко изложить это для вас. Мы определяем ...
рекурсивный тип,
Tree<α>
представляющий узел в двоичном дереве. Каждый узел хранитvalue
некоторый тип α и имеет ссылки на необязательныеleft
иright
поддеревья того же типа.функция
height
которая возвращает самое дальнее расстояние от любого конечного узла до корневого узлаt
.Теперь давайте превратим приведенный выше псевдокод
height
в правильный синтаксис Java! (Я буду продолжать опускать некоторые шаблоны для краткости, такие как модификаторы объектной ориентации и доступности.) Я собираюсь показать два возможных решения.1. Универсальное типовое решение:
Наиболее очевидное исправление состоит в том, чтобы просто сделать
height
generic, введя параметр типа α в его сигнатуру:Это позволит вам объявлять переменные и создавать выражения типа α внутри этой функции, если вы захотите. Но...
2. Экзистенциальное решение типа:
Если вы посмотрите на тело нашего метода, вы заметите, что мы на самом деле не обращаемся и не работаем с чем-либо типа α ! Нет ни одного выражения с таким типом, ни переменных, объявленных с этим типом ... итак, почему мы вообще должны делать
height
generic? Почему мы не можем просто забыть об α ? Как оказалось, мы можем:Как я уже писал в самом начале этого ответа, экзистенциальные и универсальные типы имеют комплементарный / двойственный характер. Таким образом, если решение универсального типа должно было сделать
height
более универсальным, то следует ожидать, что экзистенциальные типы будут иметь противоположный эффект: сделать его менее универсальным, а именно скрыть / удалить параметр типа α .Как следствие, вы больше не можете ссылаться на тип
t.value
в этом методе и манипулировать какими-либо выражениями этого типа, потому что с ним не связан ни один идентификатор. (?
Подстановочный знак - это специальный токен, а не идентификатор, который «захватывает» тип.)t.value
Фактически стал непрозрачным; пожалуй, единственное, что вы все еще можете сделать с ним, это набрать егоObject
.Резюме:
источник
Object
довольно интересным: хотя оба они похожи в том, что они позволяют писать статически независимый от типа код, первое (обобщение) не просто выбрасывает почти всю доступную информацию о типе в достичь этой цели. В этом конкретном смысле дженерики являются средством защитыObject
ИМО.public static void swap(List<?> list, int i, int j) { swapHelper(list, i, j); } private static <E> void swapHelper(List<E> list, int i, int j) { list.set(i, list.set(j, list.get(i))); }
,E
этоuniversal type
и?
представляет собойexistential type
??
in по-int height(Tree<?> t)
прежнему неизвестен внутри функции и все еще определяется вызывающей стороной, поскольку именно вызывающая сторона должна выбирать, какое дерево передавать. Даже если люди называют это экзистенциальным типом в Java, это не так.?
Заполнитель может быть использован для реализации формы экзистенциалов в Java, в некоторых случаях, но это не один из них.Это все хорошие примеры, но я решил ответить на это немного по-другому. Напомним из математики, что ∀x. P (x) означает «для всех x, я могу доказать, что P (x)». Другими словами, это своего рода функция, вы даете мне крестик, и у меня есть метод, чтобы доказать это для вас.
В теории типов мы говорим не о доказательствах, а о типах. Таким образом, в этом пространстве мы имеем в виду «для любого типа X, который вы мне дадите, я дам вам конкретный тип P». Теперь, поскольку мы не даем P много информации о X, кроме того факта, что это тип, P не может ничего с этим поделать, но есть несколько примеров. P может создать тип «всех пар одного и того же типа»:
P<X> = Pair<X, X> = (X, X)
. Или мы можем создать опцию type:,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 Р, (чтобы мы знали все о Х, но Р очень мало), теперь все наоборот.P<X>
не обещает давать какую-либо информацию о X, только то, что там есть один, и что это действительно тип.Чем это полезно? Ну, P может быть типом, который имеет способ раскрытия своего внутреннего типа X. Примером может быть объект, который скрывает внутреннее представление своего состояния X. Хотя у нас нет никакого способа непосредственно манипулировать им, мы можем наблюдать его эффект, poking at P. Может быть много реализаций этого типа, но вы можете использовать все эти типы независимо от того, какой из них был выбран.
источник
P<X>
вместоP
(та же функциональность и тип контейнера, скажем, но вы не знаете, что она содержитX
)?∀x. P(x)
ничего не значит о доказуемостиP(x)
, только правда.Чтобы прямо ответить на ваш вопрос:
В универсальном типе использование
T
должно включать параметр типаX
. НапримерT<String>
илиT<Integer>
. Для использования экзистенциального типаT
не включайте этот параметр типа, потому что он неизвестен или не имеет значения - просто используйтеT
(или в JavaT<?>
).Дальнейшая информация:
Универсальные / абстрактные типы и экзистенциальные типы - это двойственная перспектива между потребителем / клиентом объекта / функции и производителем / реализацией этого. Когда одна сторона видит универсальный тип, другая видит экзистенциальный тип.
В Java вы можете определить универсальный класс:
MyClass
,T
является универсальным , так как вы можете заменить любой тип дляT
когда вы используете этот класс , и вы должны знать фактический тип T всякий раз , когда вы используете экземплярMyClass
MyClass
сам по себе,T
является экзистенциальным, потому что он не знает реальный типT
?
представляет экзистенциальный тип - таким образом, когда вы находитесь внутри класса,T
в основном?
. Если вы хотите обработать экземплярMyClass
с помощьюT
existential, вы можете объявить,MyClass<?>
как вsecretMessage()
примере выше.Экзистенциальные типы иногда используются, чтобы скрыть детали реализации чего-либо, как обсуждалось в другом месте. Java-версия этого может выглядеть так:
Немного сложно понять это правильно, потому что я притворяюсь, что я использую какой-то функциональный язык программирования, а не Java. Но дело здесь в том, что вы фиксируете какое-то состояние и список функций, которые работают с этим состоянием, и вы не знаете реальный тип части состояния, но функции знают, так как они уже сопоставлены с этим типом. ,
Теперь в Java все не финальные не примитивные типы являются частично экзистенциальными. Это может показаться странным, но поскольку переменная, объявленная как,
Object
потенциально может быть подклассомObject
, вы не можете объявить определенный тип, только «этот тип или подкласс». Итак, объекты представлены как бит состояния плюс список функций, которые работают с этим состоянием - какая именно функция вызывается, определяется во время выполнения поиском. Это очень похоже на использование экзистенциальных типов выше, где у вас есть часть экзистенциального состояния и функция, которая работает с этим состоянием.В статически типизированных языках программирования без подтипов и приведений экзистенциальные типы позволяют управлять списками объектов различного типа. Список
T<Int>
не может содержатьT<Long>
. Однако списокT<?>
может содержать любой вариантT
, позволяющий помещать в список множество различных типов данных и преобразовывать их все в целое число (или выполнять любые операции, предусмотренные в структуре данных) по требованию.Можно в значительной степени всегда конвертировать запись с экзистенциальным типом в запись без использования замыканий. Закрытие также типизировано экзистенциально, так как свободные переменные, по которым оно закрыто, скрыты от вызывающей стороны. Таким образом, язык, который поддерживает замыкания, но не экзистенциальные типы, позволяет вам создавать замыкания, которые имеют то же скрытое состояние, которое вы бы поместили в экзистенциальную часть объекта.
источник
Экзистенциальный тип - это непрозрачный тип.
Подумайте о дескрипторе файла в Unix. Вы знаете, что его тип int, поэтому вы можете легко его подделать. Например, вы можете попробовать прочитать из дескриптора 43. Если так получилось, что у программы есть файл, открытый с помощью этого конкретного дескриптора, вы будете читать из него. Ваш код не должен быть вредоносным, просто небрежным (например, дескриптор может быть неинициализированной переменной).
Экзистенциальный тип скрыт от вашей программы. Если
fopen
возвращен экзистенциальный тип, все, что вы можете с ним сделать - это использовать его с некоторыми библиотечными функциями, которые принимают этот экзистенциальный тип. Например, следующий псевдокод будет компилироваться:Интерфейс "read" объявлен как:
Существует тип T такой, что:
Переменная exfile - это не int, не a
char*
, не struct file - ничего, что вы можете выразить в системе типов. Вы не можете объявить переменную, тип которой неизвестен, и вы не можете привести, скажем, указатель к этому неизвестному типу. Язык не позволит вам.источник
read
is,∃T.read(T file, ...)
тогда нет ничего, что вы могли бы передать в качестве первого параметра. Что бы сработало -fopen
вернуть дескриптор файла и функцию чтения в одной и той же экзистенциальной∃T.(T, read(T file, ...))
Кажется, я немного опаздываю, но в любом случае, этот документ добавляет другое представление о том, что такое экзистенциальные типы, хотя они не являются специфически независимыми от языка, тогда должно быть довольно легко понять экзистенциальные типы: http: //www.cs.uu .nl / groups / ST / Projects / ehc / ehc-book.pdf (глава 8)
источник
Универсальный тип существует для всех значений параметра (ов) типа. Экзистенциальный тип существует только для значений параметров типа (ов), которые удовлетворяют ограничениям экзистенциального типа.
Например, в Scala одним из способов выражения экзистенциального типа является абстрактный тип, который ограничен некоторыми верхними или нижними границами.
Эквивалентно ограниченный универсальный тип - это экзистенциальный тип, как в следующем примере.
Любое использование сайта может использовать,
Interface
потому что любые инстанцируемые подтипыExistential
должны определять,type Parameter
который должен реализовыватьInterface
.Вырожденный случай экзистенциального типа в Scala является абстрактным типом , который никогда не упоминается и , следовательно , не должно быть определен любым подтипом. Это эффективно имеет сокращенную запись
List[_]
в Scala иList<?>
в Java.Мой ответ был вдохновлен предложением Мартина Одерского объединить абстрактные и экзистенциальные типы. Сопровождающий слайд помогает пониманию.
источник
∀x.f(x)
непрозрачны для своих принимающих функций, в то время как экзистенциальные типы∃x.f(x)
ограничены определенными свойствами. Как правило, все параметры являются экзистенциальными, так как их функция будет манипулировать ими напрямую; однако универсальные параметры могут иметь типы, которые являются универсальными, так как функция не будет управлять ими, кроме базовых универсальных операций, таких как получение ссылки, как в:∀x.∃array.copy(src:array[x] dst:array[x]){...}
forSome
для экзистенциального количественного определения параметров типа.Исследования абстрактных типов данных и сокрытия информации привели экзистенциальные типы в языки программирования. Создание абстрактного типа данных скрывает информацию об этом типе, поэтому клиент этого типа не может злоупотреблять им. Скажем, у вас есть ссылка на объект ... некоторые языки позволяют вам преобразовать эту ссылку в ссылку на байты и делать все, что вы хотите, с этим фрагментом памяти. В целях гарантии поведения программы для языка полезно обеспечить, чтобы вы действовали только в отношении ссылки на объект с помощью методов, предоставляемых конструктором объекта. Вы знаете, что тип существует, но не более того.
источник
Я создал эту диаграмму. Я не знаю, строго ли это. Но если это поможет, я рад.
источник
Как я понимаю, это математический способ описания интерфейсов / абстрактного класса.
Что касается T = ∃X {X a; int f (X); }
Для C # это будет переводиться в общий абстрактный тип:
«Экзистенциальный» просто означает, что существует некоторый тип, который подчиняется определенным здесь правилам.
источник