Что делает ключевое слово `forall` в Haskell/GHC?
Я начинаю понимать, как forall
Ключевое слово используется в так называемых "экзистенциальных типах", таких как:
data ShowBox = forall s. Show s => SB s
Это только подмножество того, как 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
материал...
Я предпочитаю ясный, свободный от жаргонного языка английский язык, а не тот язык, который является нормальным в академической среде. Большинство объяснений, которые я пытаюсь прочитать по этому поводу (те, которые я могу найти в поисковых системах), имеют следующие проблемы:
- Они неполные. Они объясняют одну часть использования этого ключевого слова (например, "экзистенциальные типы"), что заставляет меня чувствовать себя счастливым, пока я не прочитаю код, который использует его совершенно другим способом (например,
runST
,foo
а такжеbar
выше). - Они переполнены предположениями, которые я читал последними в любой области дискретной математики, теории категорий или абстрактной алгебры, популярной на этой неделе. (Если я никогда не прочитаю слова "проконсультируйся с бумагой о деталях реализации" снова, это будет слишком рано.)
- Они написаны способами, которые часто превращают даже простые понятия в извилистую и искаженную грамматику и семантику.
Так...
На актуальный вопрос. Кто-нибудь может полностью объяснить forall
ключевое слово на чистом, простом английском языке (или, если оно где-то существует, указывает на такое ясное объяснение, которое я пропустил), которое не предполагает, что я математик, погруженный в жаргон?
Отредактировано, чтобы добавить:
Ниже были два выдающихся ответа из более качественных, но, к сожалению, я могу выбрать только один как лучший. Ответ Нормана был подробным и полезным, объясняя вещи таким образом, чтобы показать некоторые теоретические основы forall
и в то же время показывая мне некоторые практические последствия этого. Ответ Яирчу охватил область, которую никто больше не упоминал (переменные типа scoped), и проиллюстрировал все концепции с помощью кода и сеанса 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. Для его поддержки требуется расширение forall
ключевое слово.
В основном, есть 3 различных общих использования для forall
ключевое слово (или, по крайней мере, так кажется), и у каждого есть свое собственное расширение Haskell: ScopedTypeVariables
, RankNTypes
/ Rank2Types
, ExistentialQuantification
,
Приведенный выше код не получает синтаксическую ошибку с одним из этих включенных, но только проверки типа с ScopedTypeVariables
включен.
Переменные типа Scoped:
Переменные типа Scoped помогают определить типы для кода внутри where
статьи. Это делает b
в val :: b
тот же, что и b
в foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
,
Заблуждение: вы можете услышать это, когда опускаете forall
от типа это на самом деле все еще неявно там. ( из ответа Нормана: "как правило, эти языки не включают в себя полиморфные типы"). Это утверждение верно, но оно относится к другим forall
а не к ScopedTypeVariables
использовать.
Ранг-N-типов:
Давайте начнем с этого mayb :: b -> (a -> b) -> Maybe a -> b
эквивалентно mayb :: forall a b. b -> (a -> b) -> Maybe a -> b
кроме случаев, когда ScopedTypeVariables
включен.
Это означает, что это работает для каждого 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
на 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
Чем это отличается от Rank-N-Types?
ghci> :set -XRankNTypes
ghci> length (["Hello", "World"] :: forall a. [a])
Couldnt match expected type 'a' against inferred type '[Char]'
...
С рангом N-типов, forall a
означало, что ваше выражение должно соответствовать всем возможным a
s. Например:
ghci> length ([] :: forall a. [a])
0
Пустой список работает как список любого типа.
Так с экзистенциальной квантификацией, forall
в data
Определения означают, что содержащееся значение может иметь любой подходящий тип, но не все подходящие типы.
Кто-нибудь может полностью объяснить ключевое слово forall в ясном, простом английском языке?
Нет. (Ну, может, Дон Стюарт сможет.)
Вот барьеры для простого, ясного объяснения или forall
:
Это квантификатор. У вас должна быть хотя бы небольшая логика (исчисление предикатов), чтобы увидеть универсальный или экзистенциальный квантификатор. Если вы никогда не видели исчисление предикатов или вам не нравятся квантификаторы (и я видел во время отборочных экзаменов кандидатов, которые не устраивают), то для вас нет простого объяснения
forall
,Это квантификатор типа. Если вы еще не видели System F и не научились писать полиморфные типы, вы найдете
forall
сбивает с толку. Опыт работы с Haskell или ML недостаточен, потому что обычно в этих языкахforall
из полиморфных типов. (На мой взгляд, это ошибка дизайна языка.)В частности, в Хаскеле
forall
используется таким образом, что я нахожу запутанным. (Я не теоретик типов, но моя работа приводит меня в соприкосновение с большой теорией типов, и я вполне доволен ею.) Для меня основной источник путаницы заключается в том, чтоforall
используется для кодирования типа, который я сам предпочел бы написать сexists
, Это оправдано хитрым изоморфизмом типов, включающим квантификаторы и стрелки, и каждый раз, когда я хочу это понять, мне приходится искать вещи и самостоятельно разрабатывать изоморфизм.Если вас не устраивает идея изоморфизма типов или если у вас нет практики думать об изоморфизмах типов, это использование
forall
собирается помешать вам.Хотя общая концепция
forall
всегда одно и то же (привязка к введению переменной типа), детали различного использования могут значительно различаться. Неформальный английский не очень хороший инструмент для объяснения вариаций. Чтобы действительно понять, что происходит, вам нужна математика. В этом случае соответствующую математику можно найти во вводном тексте Бенджамина Пирса " Типы и языки программирования", который является очень хорошей книгой.
Что касается ваших конкретных примеров,
runST
должен сделать вашу голову болит. Типы высшего ранга (слева от стрелки) редко встречаются в дикой природе. Я рекомендую вам прочитать статью, которая представила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
являютсяid
или функция, которая всегда расходится или ошибки, какundefined
, Причина в том, что сfoo
,forall
слева от стрелки, так что вызывающийfoo
Я не могу выбрать чтоa
скорее - это реализацияfoo
что выбирает чтоa
является. Так какforall
слева от стрелки, а не над стрелкой, как вbar
инстанцирование происходит в теле функции, а не в месте вызова.
Резюме: полное объяснение forall
Ключевое слово требует математики и может быть понято только тем, кто изучал математику. Даже частичные объяснения трудно понять без математики. Но, возможно, мои частичные, не математические объяснения немного помогут. Читайте Лончбери и Пейтон Джонс runST
!
Приложение: Жаргон "вверху", "внизу", "слева от". Они не имеют ничего общего с текстовыми способами написания типов и не имеют ничего общего с деревьями абстрактного синтаксиса. В абстрактном синтаксисе 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
тип в скобках будет называться "поле слева от стрелки". (Я использую такие типы в оптимизаторе, над которым я работаю.)
Мой оригинальный ответ:
Кто-нибудь может полностью объяснить ключевое слово forall в ясном, простом английском
Как указывает Норман, очень трудно дать ясное, ясное английское объяснение технического термина из теории типов. Мы все пытаемся все же.
Есть только одна вещь, которую нужно помнить о "forall": он привязывает типы к некоторой области видимости. Как только вы это понимаете, все довольно просто. Это эквивалент "лямбды" (или формы "пусть") на уровне типов - Норман Рэмси использует понятие "слева"/"выше", чтобы передать эту же концепцию объема в своем превосходном ответе.
Большинство вариантов использования "forall" очень просты, и вы можете найти их в Руководстве пользователя GHC, S7.8., В частности, в превосходном S7.8.5 для вложенных форм "forall".
В Haskell мы обычно оставляем связыватель для типов, когда тип определяется количественно, например:
length :: forall a. [a] -> Int
эквивалентно:
length :: [a] -> Int
Вот и все.
Поскольку теперь вы можете привязать переменные типа к некоторой области видимости, у вас могут быть области, отличные от верхнего уровня (" универсально измеренный"), как в первом примере, где переменная типа видна только в структуре данных. Это позволяет скрытые типы (" экзистенциальные типы"). Или мы можем иметь произвольную вложенность привязок ("ранг N типов").
Чтобы глубоко понять системы типов, вам нужно выучить некоторый жаргон. Это природа информатики. Тем не менее, простое использование, как и выше, должно быть в состоянии понять интуитивно, по аналогии с "let" на уровне значения. Отличное введение - Лончбери и Пейтон Джонс.
Вот быстрое и грязное объяснение в простых выражениях, с которым вы, вероятно, уже знакомы.
forall
Ключевое слово действительно используется только одним способом в Haskell. Это всегда означает одно и то же, когда вы видите это.
Универсальное количественное определение
Универсально выраженный тип - это тип формы 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
например. Но foo
это отличается. Это требует, чтобы реализация 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
довольно ясно в отношении универсального количественного определения, и в этом контексте термин экзистенциальный также имеет больше смысла, хотя было бы менее неловко, если бы exists
ключевое слово. То, является ли квантификация эффективно универсальной или экзистенциальной, зависит от расположения квантификатора относительно того, где переменные используются по какой стороне стрелки функции, и все это немного сбивает с толку.
Так что, если это не помогает, или если вам просто не нравится символическая логика, с более функциональной точки зрения программирования вы можете рассматривать переменные типа как просто (неявные) параметры типа для функции. Функции, принимающие параметры типа в этом смысле, традиционно пишутся с использованием прописной лямбды по любой причине, которую я напишу здесь как /\
,
Итак, рассмотрим id
функция:
id :: forall a. a -> a
id x = x
Мы можем переписать его как лямбду, убрав "параметр типа" из сигнатуры типа и добавив встроенные аннотации типов:
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
как аргумент зависит от bar
Тип параметра. Подумайте, было ли у вас что-то вроде этого:
bar2 = /\a -> (\f -> (f 't', True)) :: (a -> a) -> (Char, Bool)
Вот bar2
применяет функцию к чему-то типа Char
так давай bar2
любой тип параметра, кроме Char
вызовет ошибку типа.
С другой стороны, вот что foo
может выглядеть так:
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 на ясном, простом английском языке (или, если оно где-то существует, указать на такое ясное объяснение, которое я пропустил), которое не предполагает, что я математик, погруженный в жаргон?
Я собираюсь попытаться объяснить только смысл и, возможно, применение forall
в контексте Haskell и его систем типов.
Но прежде чем вы поймете, что я хотел бы направить вас к очень доступной и приятной беседе Рунара Бьярнасона под названием " Освобождение от ограничений, ограничение свобод". Доклад полон примеров из реальных случаев использования, а также примеров в Scala в поддержку этого утверждения, хотя он не упоминает forall
, Я постараюсь объяснить forall
перспектива ниже.
CONSTRAINTS LIBERATE, LIBERTIES CONSTRAIN
Очень важно переварить и поверить в это утверждение, чтобы перейти к следующему объяснению, поэтому я призываю вас посмотреть выступление (по крайней мере, его части).
Теперь очень распространенный пример, показывающий выразительность системы типов Haskell, - это сигнатура типа:
foo :: a -> a
Говорят, что с учетом этой сигнатуры типа существует только одна функция, которая может удовлетворить этот тип, и это identity
функция или что более известно id
,
На начальных этапах изучения Хаскелла я всегда интересовался следующими функциями:
foo 5 = 6
foo True = False
они оба удовлетворяют вышеуказанной сигнатуре типа, тогда почему люди из Haskell утверждают, что это id
один, который удовлетворяет сигнатуре типа?
Это потому, что есть неявное forall
скрыто в подписи типа. Фактический тип:
id :: forall a. a -> a
Итак, теперь давайте вернемся к утверждению: ограничения освобождают, ограничения свободы
Переводя это в систему типов, это утверждение становится:
Ограничение на уровне типа становится свободой на уровне термина
а также
Свобода на уровне типа становится ограничением на уровне термина
Попробуем доказать первое утверждение:
Ограничение на уровне типа.
Таким образом, наложение ограничения на нашу подпись типа
foo :: (Num a) => a -> a
становится свободой на уровне термина дает нам свободу или гибкость, чтобы написать все эти
foo 5 = 6
foo 4 = 2
foo 7 = 9
...
То же самое можно наблюдать, ограничивая a
с любым другим типом класса и т. д.
Итак, что это за тип подписи: foo :: (Num a) => a -> a
переводится как
∃a , st a -> a, ∀a ∈ Num
Это называется экзистенциальной квантификацией, что означает, что существуют некоторые случаи a
для которого функция, когда кормили что-то типа a
возвращает что-то того же типа, и все эти экземпляры принадлежат множеству чисел.
Следовательно, мы можем видеть добавление ограничения (что a
должен принадлежать к набору Numbers), освобождает термин level для нескольких возможных реализаций.
Теперь перейдем ко второму утверждению и тому, которое на самом деле несет в себе объяснение forall
:
Свобода на уровне типа становится ограничением на уровне термина
Итак, теперь давайте освободим функцию на уровне типа:
foo :: forall a. a -> a
Теперь это переводится как:
∀a , a -> a
Это означает, что реализация этой сигнатуры типа должна быть такой, чтобы a -> a
для всех обстоятельств.
Так что теперь это начинает ограничивать нас на уровне термина. Мы больше не можем писать
foo 5 = 7
потому что эта реализация не будет удовлетворять, если мы положим a
как Bool
, a
может быть Char
или [Char]
или пользовательский тип данных. При любых обстоятельствах он должен возвращать что-то похожего типа. Эта свобода на уровне типов - это то, что известно как Универсальное Количественное определение, и единственная функция, которая может удовлетворить это
foo a = a
который обычно известен как identity
функция
следовательно forall
это liberty
на уровне типа, фактическая цель которого состоит в constrain
термин уровень для конкретной реализации.
Причина, по которой это ключевое слово используется по-разному, заключается в том, что оно фактически используется как минимум в двух разных системных расширениях: типах более высокого ранга и экзистенциалах.
Вероятно, лучше просто прочитать и понять эти две вещи отдельно, а не пытаться получить объяснение того, почему "forall" является подходящим синтаксисом в обоих случаях одновременно.
Как экзистенциально экзистенциально?
С экзистенциальной квантификацией,
forall
вdata
Определения означают, что содержащееся значение может иметь любой подходящий тип, но не все подходящие типы. - ответ Ячиру
Объяснение почемуforall
вdata
определения изоморфны (exists a. a)
(псевдо-Haskell) можно найти в "Хаскеле / Экзистенциально квантифицированные типы" в wikibooks.
Ниже приводится краткое стенографическое резюме:
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
означает, что определение значения или функции должно быть полиморфным.
Если определяемая вещь является полиморфным значением, то это означает, что значение должно быть действительным для всех подходящих a
, что довольно ограничительно.
Если определяемая вещь является полиморфной функцией, то это означает, что функция должна быть действительной для всех подходящих a
, что не является ограничительным, потому что только потому, что функция полиморфна, не означает, что применяемый параметр должен быть полиморфным. То есть, если функция действительна для всех a
то наоборот любой подходящий a
может применяться к функции. Однако тип параметра может быть выбран только один раз в определении функции.
Если forall
находится внутри типа параметра функции (т.е. Rank2Type
) тогда это означает, что применяемый параметр должен быть по- настоящему полиморфным, чтобы соответствовать идее forall
означает, что определение полиморфно. В этом случае тип параметра может быть выбран более одного раза в определении функции ( "и выбирается реализацией функции", как указано Норманом)
Поэтому причина существования data
определения позволяет любому a
это потому, что конструктор данных является полиморфной функцией:
MkT :: forall a. a -> T
вид MkT:: a -> *
Что означает любой a
может применяться к функции. В противоположность, скажем, полиморфному значению:
valueT :: forall a. [a]
вид значения T:: a
Это означает, что определение valueT должно быть полиморфным. В этом случае, valueT
можно определить как пустой список []
всех типов.
[] :: [t]
Различия
Хотя значение для forall
соответствует в ExistentialQuantification
а также RankNType
экзистенциал имеет разницу, так как data
Конструктор может быть использован в сопоставлении с образцом. Как описано в руководстве пользователя GHC:
При сопоставлении с образцом каждое сопоставление с образцом вводит новый, отдельный тип для каждой переменной экзистенциального типа. Эти типы не могут быть объединены с любым другим типом, и при этом они не могут выходить из области соответствия шаблона.