Що робить ключове слово `forall` у Haskell / GHC?


312

Я починаю розуміти, як 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матеріал ...

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

  1. Вони неповні. Вони пояснюють одну частину використання цього ключового слова (наприклад , «екзистенційні типи») , яка змушує мене відчувати себе щасливим , поки я не прочитав код , який використовує його в абсолютно по- іншому (як runST, fooі barвище).
  2. Вони щільно переповнені припущеннями, що я читав останнє в будь-якій галузі дискретної математики, теорії категорій або абстрактної алгебри, популярні на цьому тижні. (Якщо я ніколи не прочитаю слів "проконсультуйтеся в документі, будь ласка, щодо деталей реалізації", це буде занадто рано.)
  3. Вони написані способами, які часто перетворюють навіть прості поняття в закрутнену і переломову граматику та семантику.

Тому...

На власне питання. Чи може хтось повністю пояснити forallключове слово чіткою, простою англійською мовою (або, якщо вона десь існує, вказати на таке чітке пояснення, яке я пропустив), що не припускає, що я математик, пронизаний жаргоном?


Відредаговано, щоб додати:

Нижче було два відмінних відповіді з більш якісних, але, на жаль, я можу вибрати лише один як найкращий. Відповідь Нормана була детальною і корисною, пояснюючи речі таким чином, що показувала деякі теоретичні основи forallі в той же час показувала мені деякі практичні наслідки цього. відповідь yairchuохоплював область, про яку ніхто інший не згадував (змінні типу типу) та ілюстрував усі концепції з кодом та сеансом GHCi. Якби можна було вибрати обидва як найкращих, я б. На жаль, я не можу, і, уважно переглянувши обидві відповіді, я вирішив, що yairchu трохи перевершує Норман через ілюстративний код та додане пояснення. Однак це трохи несправедливо, тому що насправді мені потрібні були обидві відповіді, щоб зрозуміти це до того моменту, що forallне залишає мене слабкого почуття страху, коли я бачу це в підписі типу.


7
Вікі Haskell здаються досить привітними для початківців на цю тему.
jhegedus

Відповіді:


263

Почнемо з прикладу коду:

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 допомагають задати типи коду всередині 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

Чим це відрізняється від рангів-N-типів?

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

Завдяки типу Rank-N forall aозначає, що ваше вираження повинно відповідати всім можливим as. Наприклад:

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

Порожній список працює як список будь-якого типу.

Отже, при екзистенціальному кількісному визначенні foralls у dataвизначеннях означає, що вміщене значення може бути будь-якого відповідного типу, а не те, що воно повинно бути всіх відповідних типів.


Гаразд, я отримав свої шість годин і тепер можу розшифрувати вашу відповідь. :) Між вами та Норманом я отримав саме таку відповідь, яку шукав. Дякую.
ДАЙТЕ МОЕ правильне ДУМКА

2
Насправді ви ScopedTypeVariablesздаєтесь гіршими, ніж є. Якщо ви пишете тип b -> (a -> b) -> Maybe a -> bіз цим розширенням на ньому, він все одно буде рівнозначним forall a b. b -> (a -> b) -> Maybe a -> b. Тим НЕ менше, якщо ви хочете послатися на те ж саме b (і не неявно кількісно) , то вам потрібно записати в явному вигляді кількісних версії. Інакше STVце було б надзвичайно нав'язливим розширенням.
nominolo

1
@nominolo: Я не хотів зневажати ScopedTypeVariables, і не думаю, що це погано. imho, це дуже корисний інструмент для процесу програмування, особливо для початківців Haskell, і я вдячний, що він існує.
yairchu

2
Це досить давнє запитання (і відповідь), але, можливо, варто його оновити, щоб відобразити той факт, що екзистенційні типи можна виразити за допомогою GADT таким чином, що (як мінімум, мені) робить кількісне визначення набагато простіше зрозуміти.
dfeuer

1
Я особисто думаю, що простіше пояснити / зрозуміти екзистенційне позначення з точки зору його перекладу у форму GADT, ніж самостійно, але ви, звичайно, вільні думати інакше.
dfeuer

117

Чи може хтось повністю пояснити ключове слово forall зрозумілою, зрозумілою англійською мовою?

Ні (ну, може, Дон Стюарт може.)

Ось перешкоди для простого, чіткого пояснення або forall:

  • Це кількісний показник. Ви повинні мати хоча б трохи логіки (предикатне обчислення), щоб побачити універсальний або екзистенціальний кількісний показник. Якщо ви ніколи не бачили обчислення предикатів або вам не подобаються кількісні показники (а я бачив студентів під час кваліфікаційних іспитів докторантури, які не є зручними), то для вас, це не є легким поясненням forall.

  • Це тип квантор. Якщо ви ще не бачили System F і не почали писати поліморфні типи, ви знайдете forallзаплутаність. Досвіду роботи з Haskell або ML недостатньо, оскільки зазвичай ці мови опускають forallполіморфні типи. (На мій погляд, це помилка дизайну мови.)

  • Зокрема, Haskell 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ключового слова вимагає математики і можуть бути зрозумілі тільки тим , хто вивчав математику. Навіть часткові пояснення важко зрозуміти без математики. Але, можливо, мої часткові, не математичні пояснення трохи допомагають. Прочитайте Launchbury та Peyton Jones runST!


Додаток: Жаргон "вгорі", "внизу", "зліва від". Вони не мають нічого спільного з текстовими способами написання типів і всім відношенням до дерев абстрактних синтаксисів. В абстрактному синтаксисі a forallприймає назву змінної типу, а потім є повний тип "нижче" forall. Стрілка приймає два типи (тип аргументу та результату) та утворює новий тип (тип функції). Тип аргументу - стрілка "зліва"; це ліва дитина стрілки у дереві абстрактно-синтаксису.

Приклади:

  • В forall a . [a] -> [a], forall знаходиться над стрілкою; що ліворуч від стрілки [a].

  • В

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

    тип у круглих дужках буде називатися "заголовком ліворуч від стрілки". (Я використовую такі види в оптимізаторі, над яким я працюю.)


Насправді я отримав вище / нижче / ліворуч, не замислюючись про це. Я даремник, так, але старий тушканчик, якому раніше доводилося боротися з цими речами. (Написання укладача ASN.1 серед інших;) Хоча дякую за додаток.
ПРОСТО МОЕ правильне ДУМКУ

12
@ ДУЖЕ спасибі, але я пишу за нащадків. Я наткнувся на більше одного програміста, який думає, що в forall a . [a] -> [a], форель знаходиться зліва від стрілки.
Норман Рамзі

Гаразд, детально переглядаючи вашу відповідь, тепер я маю подякувати тобі, Норман, від щирого серця. Багато речей стала на місце гучним клацанням зараз, і речі, які я досі не розумію, я принаймні визнаю, що я не маю на увазі цього зрозуміти, і просто перейду forallв тих умовах як, фактично, лінія шум. Я перегляну цей папір, до якого ви посилаєтесь (дякую також за посилання!) Та побачу, чи це в моєму розумінні. Кудос.
ДАЙТЕ МОЕ правильне ДУМКА

10
Я читав ліворуч і дивився, буквально, ліворуч. Тож мені було зовсім незрозуміло, поки ви не сказали "дерево розбору".
Пол Натан

Завдяки вказівнику на книгу Пірса. Це дуже чітке пояснення системи F. Це пояснює, чому так existsі не було реалізовано. (Це не частина системи F!) У Haskell частина System F робиться неявною, алеforall це одна з речей, яку неможливо змістити під килим. Це так, ніби вони почали з Хіндлі-Мілнера, що дозволило forallб зробити неявне, а потім обрали більш потужну систему типу, збивши з пантелику тих, хто вивчав FOll і «існує» FOL, і там зупинилися.
T_S_

50

Моя оригінальна відповідь:

Хтось може повністю пояснити ключове слово forall зрозумілою, зрозумілою англійською мовою

Як вказує Норман, дуже важко дати чітке, зрозуміле англійське пояснення технічного терміна з теорії типів. Ми все намагаємось.

Про «forall» слід пам’ятати лише одне: воно прив'язує типи до певної сфери . Як тільки ви це зрозумієте, все досить легко. Це еквівалент "лямбда" (або форма "нехай") на рівні типу - Норман Рамзі використовує поняття "ліворуч" / "вище", щоб передати цю саму концепцію обсягу у своїй чудовій відповіді .

Більшість застосувань «forall» дуже прості, і їх можна знайти в Посібнику користувача GHC, S7.8 ., Особливо відмінному S7.8.5 для вкладених форм "forall".

У Haskell ми зазвичай залишаємо в'яжучу для типів, коли тип є загальнокваліфікованим, наприклад:

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

еквівалентно:

length :: [a] -> Int

Це воно.

Оскільки тепер ви можете прив’язати змінні типу до якоїсь області, ви можете мати діапазони, відмінні від верхнього рівня (" універсально кількісно оцінені "), як ваш перший приклад, де змінна тип видно лише в структурі даних. Це дозволяє приховати типи (" екзистенційні типи "). Або ми можемо мати довільне вкладення прив’язок ("ранг N типів").

Щоб глибоко зрозуміти системи типів, вам потрібно буде вивчити деякі жаргони. Така природа інформатики. Однак прості способи використання, як і вище, повинні бути зрозумілі інтуїтивно, за аналогією з «нехай» на рівні значення. Чудовий вступ є Лаунббері та Пейтон Джонс .


4
технічно length :: forall a. [a] -> Intце не еквівалентно тому, length :: [a] -> Intколи ScopedTypeVariablesце включено. Коли forall a.це є, воно впливає lengthна whereпункт (якщо він є) і змінює значення змінних типів, названих aу ньому.
yairchu

2
Справді. ScopedTypeVariables трохи ускладнює історію.
Дон Стюарт

3
@DonStewart, може "він прив'язує типи до якоїсь області", краще формулюється як "вона прив'язує змінні типу до якоїсь області" у вашому поясненні?
Ромільдо

31

Вони щільно переповнені припущеннями, що я читав останнє в будь-якій галузі дискретної математики, теорії категорій або абстрактної алгебри, популярні на цьому тижні. (Якщо я ніколи не прочитаю слів "проконсультуйтеся в документі, будь ласка, щодо деталей реалізації", це буде занадто рано.)

Е, а як щодо простої логіки першого порядку? 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параметра типу 's. Подумайте, чи натомість у вас було щось подібне:

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розширюється наскільки це можливо праворуч, аж до укладених дужок, і подібно до змінних, пов'язаних у звичайній лямбда, змінні типу, пов'язані з a, forallзнаходяться лише в області застосування в межах кількісного вираження.


Опублікувати 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 ...

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


1
Цікаве місце тут. Це дає мені ще один кут атаки на проблему, який може виявитись плідним у довгостроковій перспективі. Дякую.
ДАЙТЕ МОЕ правильне ДУМКА

@KennyTM: Або λз цього приводу, але розширення синтаксису однокодового GHC не підтримує це, оскільки λ - це лист , прикрий факт, який гіпотетично також стосуватиметься моїх гіпотетичних абстракцій великих лямбда. Звідси /\ за аналогією до \ . Я припускаю, що я міг би щойно використати, але я намагався уникнути обчислення предикатів ...
CA McCann

29

Ось швидке та брудне пояснення, зрозуміло кажучи, з яким ви, ймовірно, вже знайомі.

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, якщо Хаскела підтримує цей запис. Значення цього типу можна розглядати як пару (або "продукт"), що складається з типу 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, якби Хаскелл підтримував це позначення.

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який, безумовно, повинен якось виробляти a. Більше того, він повинен мати можливість створювати значення типу aнезалежно від того, який тип 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.


11

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

Я спробую пояснити лише значення і, можливо, застосування forallу контексті Haskell та його типів систем.

Але перш ніж зрозуміти, що я хотів би направити вас на дуже доступну і приємну розмову Рунара Б'ярнасона під назвою " Обмеження звільняються, свободи обмежують ". Розмова наповнена прикладами випадків використання в реальному світі, а також прикладів Scala на підтвердження цього твердження, хоча він не згадує forall. Я спробую пояснити forallперспективу нижче.

                CONSTRAINTS LIBERATE, LIBERTIES CONSTRAIN

Дуже важливо переварити та вважати, що це твердження продовжується з наступного пояснення, тому я закликаю вас спостерігати за розмовою (принаймні її частинами).

Зараз дуже поширений приклад, що демонструє виразність системи типу Haskell, є таким підписом:

foo :: a -> a

Кажуть, що з огляду на цей тип підпису існує лише одна функція, яка може задовольнити цей тип, і це identityфункція, або те, що більш відоме id.

На початкових етапах навчання я Haskell я завжди цікавився наступним функціям:

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повинно належати до набору чисел), звільняє рівень терміна, щоб мати кілька можливих реалізацій.


Тепер перейдемо до другого твердження та того, що насправді має пояснення forall:

Свобода на рівні типу стає обмеженням на рівні терміна

Тож тепер звільнимо функцію на рівні типу:

foo :: forall a. a -> a

Тепер це означає:

a , a -> a

що означає, що реалізація цього типу підпису повинна бути такою, щоб вона була a -> aза будь-яких обставин.

Отже, зараз це починає обмежувати нас на рівні терміну. Ми більше не можемо писати

foo 5 = 7

тому що ця реалізація не задовольнить, якщо ми поставимо її aяк a Bool. aможе бути Charабо типовим, [Char]або спеціальним типом даних. За будь-яких обставин він повинен повернути щось подібного типу. Ця свобода на рівні типу - це те, що відомо як Універсальне кількісне визначення, і єдина функція, яка може це задовольнити

foo a = a

яка загальновідома як identityфункція


Отже forall, libertyна рівні типу, фактичне призначення якого полягає constrainв терміновому рівні до певної реалізації.


9

Причиною, по якій це ключове слово є різним, є те, що він фактично використовується щонайменше у двох розширеннях системи різних типів: типи вищого рангу та екзистенціали.

Напевно, найкраще просто прочитати і зрозуміти ці дві речі окремо, а не намагатися отримати пояснення, чому "forall" є відповідним бітом синтаксису в обох одночасно.


3

Як буває екзистенційне екзистенційне?

З Екзистенціальним кількісним визначенням foralls у dataвизначеннях означає, що вміщене значення може бути будь-якого відповідного типу, а не те, що воно повинно бути всіх підходящих типів. - відповідь ячіру

Пояснення того, чому forallв dataвизначеннях є ізоморфним (exists a. a)(псевдо-Хаскелл), можна знайти у вікікнинах "Хаскелл / Екзистенційно кількісні типи" .

Далі короткий дослівний підсумок:

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на визначення та проти застосування функції .

А forallозначає визначення значення або функції повинно бути поліморфним.

Якщо річ, яка визначається, є поліморфною величиною , то це означає, що значення має бути дійсним для всіх придатних a, що є досить обмежуючим.

Якщо річ, що визначається, є поліморфною функцією , то це означає, що функція повинна бути дійсною для всіх придатних a, що не є таким обмежуючим, оскільки те, що функція є поліморфною, не означає, що застосовуваний параметр повинен бути поліморфним. Тобто, якщо функція дійсна для всіх a, то навпаки будь-яка придатна aможе бути застосована до функції. Однак тип параметра може бути обраний лише один раз у визначенні функції.

Якщо a forallзнаходиться всередині типу параметра параметра (тобто, a Rank2Type), то це означає, що застосований параметр повинен бути справді поліморфним, щоб відповідати ідеї визначенняforall засобів є поліморфним. У цьому випадку тип параметра може бути обраний не раз у визначенні функції ( "і вибирається реалізацією функції", як вказував Норман )

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

MkT :: forall a. a -> T

вид MkT :: a -> *

Що означає, що будь-який aможе бути застосований до функції. На відміну від, скажімо, поліморфного значення :

valueT :: forall a. [a]

вид valueT :: a

Що означає, що визначення значенняT повинно бути поліморфним. У цьому випадку valueTможна визначити порожній список []усіх типів.

[] :: [t]

Відмінності

Навіть незважаючи на те, що значення для forallє послідовним у, ExistentialQuantificationа RankNTypeекзистенціали мають різницю, оскільки dataконструктор може бути використаний у відповідності шаблонів. Як зазначено в посібнику користувача ghc :

Під час відповідності шаблонів кожен збіг шаблонів вводить новий, виразний тип для кожної екзистенціальної змінної типу. Ці типи не можуть бути уніфіковані з будь-яким іншим типом, і вони не можуть вийти із сфери відповідності шаблону.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.