Что делает ключевое слово "forall" в Haskell/GHC?

я начинаю понимать, как forall ключевое слово используется в так называемых "экзистенциальных типов" вроде этого:

data ShowBox = forall s. Show s => SB s

это только подмножество, однако, how forall используется, и я просто не могу обернуть свой ум вокруг его использования в таких вещах:

runST :: forall a. (forall s. ST s a) -> a

или объясняя, почему они отличаются:

foo :: (forall a. a -> a) -> (Char,Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))

и все RankNTypes вещи...

я предпочитаю ясный, свободный от жаргона английский язык, а не виды языка что нормально в академической среде. Большинство объяснений, которые я пытаюсь прочитать на этом (те, которые я могу найти через поисковые системы), имеют следующие проблемы:

  1. они неполны. Они объясняют одну часть использования этого ключевого слова( например, "экзистенциальные типы"), что заставляет меня чувствовать себя счастливым, пока я не прочитаю код, который использует его совершенно по-другому (например,runST, foo и bar выше).
  2. они плотно упакованы с предположениями, что я прочитал последний в любой отрасли дискретной математики, теории категорий или абстрактной алгебры популярен на этой неделе. (Если я никогда не читал слова " обратитесь к бумаге все для деталей реализации " опять же, это будет слишком рано.)
  3. они написаны способами, которые часто превращают даже простые понятия в мучительно искривленную и сломанную грамматику и семантику.

так...

к фактическому вопросу. Может ли кто-нибудь полностью объяснить forall ключевое слово на ясном, простом английском языке (или, если оно где-то существует, укажите на такое четкое объяснение, которое я пропустил), которое не предполагает, что я математик, погруженный в жаргон?


редактировать, чтобы добавить:

было два выдающихся ответа от более качественных ниже, но, к сожалению, я могу выбрать только один из лучших. Нормана!--43--> был подробным и полезным, объясняя вещи таким образом, что показал некоторые из теоретические основы forall и в то же время, показывая мне некоторые практические последствия этого. yairchu это!--43--> охватил область, которую никто больше не упоминал (переменные типа области видимости), и проиллюстрировал все концепции кодом и сеансом GHCi. Если бы можно было выбрать и то и другое, я бы так и сделал. К сожалению, я не могу, и, внимательно изучив оба ответа, я решил, что яирчу немного отличается от Нормана из-за иллюстративного кода и прилагается объяснение. Это немного несправедливо, однако, потому что мне действительно нужны были оба ответа, чтобы понять это до такой степени, что forall не оставляет меня со слабым чувством страха, когда я вижу его в подписи типа.

8 ответов


давайте начнем с примера кода:

foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
foob postProcess onNothin onJust mval =
    postProcess val
    where
        val :: b
        val = maybe onNothin onJust mval

этот код не компилируется (синтаксическая ошибка) в обычном Haskell 98. Для поддержки ключевое слово.

в основном, есть 3 разные общее использование для forall ключевое слово (или, по крайней мере, так он кажется), и каждый имеет свое собственное расширение Haskell:ScopedTypeVariables, RankNTypes/Rank2Types, ExistentialQuantification.

приведенный выше код не получает синтаксической ошибки с любой из них включен, но только проверки типа с ScopedTypeVariables включено.

Переменные Типа:

переменные типа помогает указать типы для код внутри where положения. Это делает b на val :: b тот же, что и в b на foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b.

запутанный момент: вы можете услышать это, когда вы опускаете forall от типа он на самом деле все еще неявно существует. (из ответа Нормана : "обычно эти языки опускают forall из полиморфных типов"). Это утверждение верно,но это относится к другим видам использования forall, а не ScopedTypeVariables использовать.

Ранг-N-Типы:

давайте начнем с этого mayb :: b -> (a -> b) -> Maybe a -> b эквивалентно mayb :: forall a b. b -> (a -> b) -> Maybe a -> b, за исключением, когда это.

это означает, что он работает для каждого a и b.

Давай вы хотите сделай что-нибудь вроде этого.
ghci> let putInList x = [x]
ghci> liftTup putInList (5, "Blah")
([5], ["Blah"])

каким должен быть тип этого liftTup? Это liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b). Чтобы понять, почему, давайте попробуем закодировать его:

ghci> let liftTup liftFunc (a, b) = (liftFunc a, liftFunc b)
ghci> liftTup (\x -> [x]) (5, "Hello")
    No instance for (Num [Char])
    ...
ghci> -- huh?
ghci> :t liftTup
liftTup :: (t -> t1) -> (t, t) -> (t1, t1)

"Хм.. почему GHC делает вывод, что кортеж должен содержать два одинаковых типа? Давайте скажем ему, что они не должны быть"

-- test.hs
liftTup :: (x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)

ghci> :l test.hs
    Couldnt match expected type 'x' against inferred type 'b'
    ...

Мда. поэтому здесь ghc не позволяет нам применять liftFunc on v, потому что v :: b и liftFunc хочет x. Мы действительно хотим, чтобы наша функция получила функцию, которая принимает любые возможно x!

{-# LANGUAGE RankNTypes #-}
liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)

так это не liftTup это работает для всех x, это функция, которую он получает, что делает.

Экзистенциальная Количественная Оценка:

давайте используем пример:

-- test.hs
{-# LANGUAGE ExistentialQuantification #-}
data EQList = forall a. EQList [a]
eqListLen :: EQList -> Int
eqListLen (EQList x) = length x

ghci> :l test.hs
ghci> eqListLen $ EQList ["Hello", "World"]
2

чем это отличается от ранга-N-типов?

ghci> :set -XRankNTypes
ghci> length (["Hello", "World"] :: forall a. [a])
    Couldnt match expected type 'a' against inferred type '[Char]'
    ...

С Рангом-N-Типов,forall a означает, что ваше выражение должно соответствовать всем возможным as. Например:

ghci> length ([] :: forall a. [a])
0

пустой список работайте как список любого типа.

так с экзистенциально-количественной оценки, foralls в data определения означают, что значение, содержащееся можете быть любой подходящий тип, не то, что это должны быть все подходящие типы.


кто-нибудь полностью объясните ключевое слово forall на ясном, простом английском языке?

нет. (ну, может быть, Дон Стюарт может.)

вот барьеры для простого, ясного объяснения или forall:

  • это Квантор. У вас должно быть хотя бы немного логики (исчисление предикатов), чтобы увидеть универсальный или экзистенциальный Квантор. Если вы никогда не видели исчисление предикатов или не комфортно с кванторами (и я видел студентов во время квалификационных экзаменов PhD, которые не удобны), то для вас нет простого объяснения forall.

  • это тип Квантор. Если вы не видели Система F и получил некоторую практику написания полиморфных типов, вы найдете forall путаешь. Опыта работы с Haskell или ML недостаточно, потому что обычно эти языки опускают forall от полиморфные типы. (На мой взгляд, это ошибка языкового дизайна.)

  • в Haskell в частности, forall используется способами, которые я нахожу запутанными. (Я не теоретик типа, но моя работа приводит меня в контакт с много теории типов, и я вполне доволен этим.) Для меня основным источником путаницы является то, что forall используется для кодирования такого типа, который я сам предпочел бы писать exists. Это оправдано хитрым типом изоморфизм, включающий кванторы и стрелки, и каждый раз, когда я хочу понять его, я должен искать вещи и сам разрабатывать изоморфизм.

    Если вам не нравится идея изоморфизма типа или если у вас нет практики, думающей об изоморфизмах типа, это использование forall собирается загнать вас в тупик.

  • в то время как общее понятие forall всегда одно и то же (привязка для введения переменной типа), детали различные пользы могут поменять значительно. Неофициальный английский язык не очень хороший инструмент для объяснения вариаций. Чтобы действительно понять, что происходит, вам нужен математике. В этом случае соответствующая математика может быть найдена во вступительном тексте Бенджамина Пирса типы и языки программирования, это очень хорошая книга.

Что касается ваших конкретных примеров,

  • runST должны сделать голова болит. Типы более высокого ранга (forall слева от стрелки) редко встречаются в дикой природе. Я призываю вас прочитать статью, которая представила runST: "Ленивые Потоки Функционального Состояния". Это действительно хорошая статья, и она даст вам гораздо лучшую интуицию для типа runST в частности и для типов более высокого ранга в целом. Объяснение занимает несколько страниц, это очень хорошо сделано, и я не собираюсь пытаться его конденсировать здесь.

  • считают

    foo :: (forall a. a -> a) -> (Char,Bool)
    bar :: forall a. ((a -> a) -> (Char, Bool))
    

    если я называю bar, я могу просто выбрать любой тип a что мне нравится, и я могу передать ему функцию от типа a типа a. Например, я могу передать функцию (+1) или reverse. Вы можете думать о forall Как сказать "я могу выбрать тип сейчас". (Техническое слово для выбора типа создание экземпляра.)

    ограничения на вызов foo гораздо строже: аргумент к foo должны быть полиморфной функции. С этим типом единственные функции, которые я могу передать в foo are id или функция, которая всегда расходится или ошибки, как undefined. Причина в том, что с foo на forall находится слева от стрелки, так как вызывающий foo я не могу выбрать, что a-а это реализация of foo это получает, чтобы выбрать, что a есть. Потому что forall находится слева от стрелки, а не над стрелкой, как в bar создание экземпляра происходит в теле функции, а не в месте вызова.

резюме: A полное объяснение forall ключевое слово требует математики и может быть понято только тем, кто изучал математику. Даже частичные объяснения трудно понять без математики. Но, может быть, мои частичные, не-математические объяснения немного помочь. Иди читай Launchbury и Пейтон Джонс на runST!


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

примеры:

  • на forall a . [a] -> [a], форалл находится над стрелкой; что слева от стрелки -[a].

  • на

    forall n f e x . (forall e x . n e x -> f -> Fact x f) 
                  -> Block n e x -> f -> Fact x f
    

    тип в круглых скобках будет называться "a forall слева от стрелки". (Я использую такие типы в оптимизаторе, с которым я работаю на.)


мой оригинальный ответ:

может ли кто-нибудь полностью объяснить ключевое слово forall на ясном, простом английском языке

Как указывает Норман, очень трудно дать ясное, простое английское объяснение технического термина из теории типов. Но мы все стараемся.

есть только одна вещь, чтобы помнить о 'Спасибо': он связывает типы с какой-то размах. Как только вы это поймете, все будет довольно просто. Это аналог "лямбда" (или форма "let") на уровне типа-Норман Рэмси использует понятие "левый" / "выше", чтобы передать эту же концепцию области в его отличный ответ.

большинство применений "forall" очень просты, и вы можете найти их в в Руководство пользователя GHC, S7.8. особенно отличный S7.8.5 на вложенные форм 'все'.

в Haskell, мы обычно выходим связыватель для типов, когда тип есть универсально quanitified, вот так:

length :: forall a. [a] -> Int

эквивалентно:

length :: [a] -> Int

вот именно.

поскольку теперь вы можете привязать переменные типа к некоторой области, у вас могут быть другие области чем верхний уровень ("квантором общности"), как и ваш первый пример, где переменная type отображается только в структуре данных. Это позволяет для скрытых типов ("экзистенциальных типов"). Или мы можем иметь произвольные гнездование of привязки ("типы ранга N").

чтобы глубоко понять системы типов, вам нужно будет изучить некоторый жаргон. Это природа информатики. Однако, простые пользы, как выше, должны быть может быть схвачен интуитивно, по аналогии с "пусть" на уровне ценности. Ля отличное введение Launchbury и Пейтон Джонс.


они плотно упакованы с предположениями, что я прочитал последние в любой отрасли дискретной математики, теории категорий или абстрактной алгебры популярен на этой неделе. (Если я никогда больше не буду читать слова "проконсультируйтесь с документом для деталей осуществления", это будет слишком рано.)

Э, А как насчет простой логики первого порядка? forall довольно ясно в отношении универсальная количественная оценка и в этом контексте термин экзистенциальные имеет больше смысла, хотя было бы менее неудобно, если бы был exists ключевое слово. Является ли квантификация фактически универсальной или экзистенциальной, зависит от размещения квантификатора относительно того, где переменные используются на какой стороне стрелки функции, и все это немного запутано.

Итак, если это не помогает, или если вам просто не нравится символическая логика, с более функциональной точки зрения программирования вы можете думать о типе переменные как просто (неявные) тип параметры функции. Функции, принимающие параметры типа в этом смысле, традиционно пишутся с использованием заглавной лямбды по любой причине, которую я напишу здесь как /\.

Итак, рассмотрим :

id :: forall a. a -> a
id x = x

мы можем переписать его как lambdas, переместив "параметр типа" из подписи типа и добавив встроенные аннотации типа:

id = /\a -> (\x -> x) :: a -> a

вот то же самое сделал const:

const = /\a b -> (\x y -> x) :: a -> b -> a

Итак,bar функция может быть чем-то вроде этого:

bar = /\a -> (\f -> ('t', True)) :: (a -> a) -> (Char, Bool)

обратите внимание, что тип функции дано bar как аргумент зависит от 'S тип. Подумайте, если бы у вас было что-то вроде этого:

bar2 = /\a -> (\f -> (f 't', True)) :: (a -> a) -> (Char, Bool)

здесь bar2 применяет функцию к чему-то типа Char, предоставив bar2 любой параметр типа, кроме Char вызовет ошибку типа.

On с другой стороны, вот что!--20--> может выглядеть так:

foo = (\f -> (f Char 't', f Bool True))

в отличие от bar, foo на самом деле не принимает никаких параметров типа на всех! Он принимает функцию, которая принимает параметр типа, а затем применяет эту функцию к двум разные типы.

поэтому, когда вы видите forall в подписи типа, просто подумайте об этом как лямбда-выражение для подписи типа. Так же, как обычные лямбды, объем forall распространяется как можно дальше вправо, вплоть до круглых скобок, и так же, как переменные, связанные в обычной лямбде, переменные типа, связанные с forall только в рамки квантором.


Post scriptum: возможно, вы могли бы задаться вопросом-теперь, когда мы думаем о функциях, принимающих параметры типа, Почему мы не можем сделать что-то более интересное с этими параметрами, чем поместить их в подпись типа? Ответ в том, что мы может!

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

Either = /\a b -> ...

но нам понадобится совершенно новая нотация, потому что так пишется такой тип, как Either a b, уже предполагает "применить функцию Either в эти параметры".

С другой стороны, функция, которая "соответствует шаблону" по своим параметрам типа, возвращая разные значения для разных типов, является метод класса типа. Небольшое расширение моего /\ синтаксис выше предлагает что-то вроде этого:

fmap = /\ f a b -> case f of
    Maybe -> (\g x -> case x of
        Just y -> Just b g y
        Nothing -> Nothing b) :: (a -> b) -> Maybe a -> Maybe b
    [] -> (\g x -> case x of
        (y:ys) -> g y : fmap [] a b g ys 
        []     -> [] b) :: (a -> b) -> [a] -> [b]

лично я думаю, что предпочитаю фактический синтаксис Haskell...

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


вот быстрое и грязное объяснение в простых терминах, с которыми вы, вероятно, уже знакомы.

на forall ключевое слово действительно используется только одним способом в Haskell. Это всегда означает одно и то же, когда вы видите это.

универсальная количественная оценка

A квантором общности типа тип формы forall a. f a. Значение этого типа можно рассматривать как функция что берет тип a в качестве аргумента и возвращает стоимостью типа f a. За исключением того, что в Haskell эти аргументы типа передаются неявно системой типа. Эта "функция" должна давать вам одно и то же значение независимо от того, какой тип она получает, поэтому значение полиморфных.

рассмотрим, например, типа forall a. [a]. Значение этого типа принимает другой тип a и дает вам список элементов того же типа a. Там это, конечно, только одна возможная реализация. Он должен был бы дать вам пустой список, потому что a может быть абсолютно любого типа. Пустой список-это единственное значение списка, которое является полиморфным по типу элемента (поскольку у него нет элементов).

или типа forall a. a -> a. Вызывающий такую функцию предоставляет как тип a и значение типа a. Затем реализация должна вернуть значение того же типа a. Есть только одна возможная реализация. Он пришлось бы вернуть то же значение, что и было дано.

экзистенциальной квантификации

Теги экзистенциально количественно типов имеет вид exists a. f a, если Haskell поддерживает эту нотацию. Значение этого типа можно рассматривать как пара (или" продукт"), состоящий из типа a и значение типа f a.

например, если у вас есть значение типа exists a. [a], у вас есть список элементов какого-то типа. Это может быть любой тип, но даже если вы не знаете, что это такое, вы можете многое сделать с таким списком. Вы можете изменить его, или подсчитать количество элементов, или выполнить любую другую операцию списка, которая не зависит от типа элементов.

хорошо, подожди минутку. Почему Haskell использует forall чтобы обозначить "экзистенциальный" тип, как показано ниже?

data ShowBox = forall s. Show s => SB s

это может быть запутанным, но это действительно с описанием тип данных конструктор SB:

SB :: forall s. Show s => s -> ShowBox

после создания вы можете придумать значение типа ShowBox как состоящий из двух вещей. Это тип s вместе со значением типа s. Другими словами, это значение экзистенциально количественно типов. ShowBox действительно можно написать как exists s. Show s => s, если Haskell поддерживает эту нотацию.

runST и друзьями

учитывая это, как это другой?

foo :: (forall a. a -> a) -> (Char,Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))

давайте сначала возьмем bar. Требуется тип a и функция типа a -> a, и производит значение типа (Char, Bool). Мы могли бы выбрать Int как a и дайте ему функцию типа Int -> Int например. Но!--35--> разное. Это требует, чтобы реализация foo смогл пройти любой тип он хочет к функции мы даем его. Таким образом, единственная функция, которую мы могли бы разумно дать, это id.

теперь мы должны быть в состоянии решить значение типа runST:

runST :: forall a. (forall s. ST s a) -> a

так runST должен быть в состоянии создать значение типа a, независимо от того, какой тип мы даем как a. Для этого ему нужен аргумент типа forall s. ST s a который под капотом просто функция типа forall s. s -> (a, s). Тогда эта функция должна иметь возможность создавать значение типа (a, s) независимо от того, какой тип реализации runST решает дать как s.

ОК, и что? Благо что это накладывает ограничение на вызывающего runST во что то типа a не может привлечь типа s на всех. Вы не можете передать ему значение типа ST s [s], например. На практике это означает, что реализация runST свободно выполнять мутацию со значением типа s. Система типов гарантирует, что эта мутация является локальной для реализации runST.

тип runST пример ранг-2 полиморфных тип потому что тип его аргумента содержит forall Квантор. Тип foo выше также имеет ранг 2. Обычный полиморфный тип, как у bar, является рангом-1, но он становится рангом-2, Если типы аргументов должны быть полиморфными, со своими forall Квантор. И если функция принимает аргументы ранга-2, то ее тип-ранг-3 и так далее. В общем, тип, который принимает полиморфные аргументы ранга n имеет ранг n + 1.


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

вероятно, лучше всего просто прочитать и понять эти две вещи отдельно, а не пытаться получить объяснение того, почему "forall" является подходящим битом синтаксиса в обоих одновременно.


может ли кто-нибудь полностью объяснить ключевое слово forall на ясном, простом английском языке (или, если оно где-то существует, указать на такое ясное объяснение, которое я пропустил), что не предполагает, что я математик, погруженный в жаргон?

я собираюсь попытаться объяснить только смысл и, возможно, применение forall в контексте Haskell и его систем типов.

но прежде чем вы поймете, что я хотел бы направить вас к очень доступный и приятный разговор Рунара Бьярнасона под названием"Ограничения Освобождают, Свободы Ограничивают". Разговор полон примеров из реальных случаев использования, а также примеров в Scala для поддержки этого утверждения, хотя в нем не упоминается forall. Я попытаюсь объяснить forall перспектива ниже.

                CONSTRAINTS LIBERATE, LIBERTIES CONSTRAIN

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

теперь очень распространенным примером, показывающим выразительность системы типов Haskell, является подпись этого типа:

foo :: a -> a

говорят, что, учитывая сигнатуру этого типа, есть только одна функция, которая может удовлетворить этот тип, и это

foo 5 = 7

потому что эта реализация не удовлетворит, если мы поставим a как Bool. a может быть Char или [Char] или пользовательский тип данных. Под всеми обстоятельства должны вернуть что-то подобное. Эта свобода на уровне типа-это то, что известно как универсальная квантификация, и единственная функция, которая может удовлетворить это

foo a = a

, который широко известный как identity функции


отсюда forall это liberty на уровне типа, фактической целью которой является constrain уровень термина для конкретной реализации.


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

С Экзистенциальной Квантификацией,foralls в data определения означают что значение, содержащееся can быть любой соответствующий тип, не вот оно!--41-->должны быть все подходящие типы. -- ответ ячиру

объяснение, почему forall на data определения изоморфна (exists a. a) (псевдо-Haskell) может быть найдено в wikibooks "Haskell/экзистенциально количественные типы".

ниже приводится краткое резюме дословно:

data T = forall a. MkT a -- an existential datatype
MkT :: forall a. a -> T -- the type of the existential constructor

при сопоставлении шаблонов / деконструкции MkT x, что то типа x?

foo (MkT x) = ... -- -- what is the type of x?

x может быть любого типа (как говорится в forall), и поэтому это тип:

x :: exists a. a -- (pseudo-Haskell)

следовательно, изоморфны:

data T = forall a. MkT a -- an existential datatype
data T = MkT (exists a. a) -- (pseudo-Haskell)

forall означает forall

мой простая интерпретация всего этого такова:"forall действительно означает "для всех"". Важным различием является влияние forall на определение против функции приложение.

A forall означает определение значения или функции должны быть полиморфными.

если определяемая вещь является полиморфной стоимостью, то это означает, что значение должно быть действительным для всех подходит a, что весьма ограничительно.

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

если forall находится внутри типа параметра функции (т. е. a Rank2Type), значит применяется должен быть истинно полиморфный, чтобы соответствовать идее forall означает определение полиморфна. В этом случае тип параметра можно выбрать несколько раз в определении функции ( "и выбирается путем реализации функции", как указал Норман)

следовательно, причина, почему экзистенциальный data определений позволяет любой a - это потому, что конструктор данных является полиморфным функции:

MkT :: forall a. a -> T

вид MkT ::a -> *

это означает, что любой a может применяться к функции. В отличие от, скажем, полиморфного стоимостью:

valueT :: forall a. [a]

вид valueT :: a

что означает определение valueT должен быть полиморфным. В этом случае valueT можно определить как пустой список [] всех видов.

[] :: [t]

различия

хотя значение для forall последовательно ExistentialQuantification и RankNType, экзистенциалы есть разница с data конструктор может использоваться в сопоставлении шаблонов. Как документально в ghc руководство пользователя:

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