Чи корисний параметричний поліморфізм вищого рангу?


16

Я впевнений, що всі знайомі із загальними методами форми:

T DoSomething<T>(T item)

Цю функцію також називають параметрично поліморфною (ПП), конкретно ПП -1-го рангу .

Скажімо, цей метод можна представити за допомогою об'єкта функції форми:

<T> : T -> T

Тобто <T>означає, що він приймає параметр одного типу і T -> Tозначає, що він приймає один параметр типу Tі повертає значення того ж типу.

Тоді наступною була б функція ПП 2-го рангу:

(<T> : T -> T) -> int 

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

Ця функція дійсно рідкісна серед мов програмування. Навіть Haskell не дозволяє це за замовчуванням.

Це корисно? Чи може він описати поведінку, яку важко описати інакше?

Крім того, що означає щось непередбачуване ? (в цьому контексті)


1
Цікаво, що TypeScript є однією основною мовою з повною підтримкою PP-PP. Наприклад, дійсний код TypeScript:let sdff = (g : (f : <T> (e : T) => void) => void) => {}
GregRos

Відповіді:


11

Взагалі, ви використовуєте поліморфізм вищого рангу, коли хочете, щоб викликав можливість вибору значення параметра типу, а не абонента . Наприклад:

f :: (forall a. Show a => a -> Int) -> (Int, Int)
f g = (g "one", g 2)

Будь-яка функція, gяку я передаю для цього, fповинна бути в змозі дати мені Intзначення якогось типу, де єдине, що gвідомо про цей тип, - це те, що він має екземпляр Show. Отже, це кошерні:

f (length . show)
f (const 42)

Але це не такі:

f length
f succ

Одним з особливо корисних застосувань є використання розміщення типів для забезпечення розміщення значень . Припустимо, у нас є об'єкт типу Action<T>, який представляє дію, яку ми можемо виконати для отримання результату типу T, такого як майбутній або зворотний виклик.

T runAction<T>(Action<T>)

runAction :: forall a. Action a -> a

А тепер припустимо, що у нас також є Actionрозміщення Resource<T>об'єктів:

Action<Resource<T>> newResource<T>(T)

newResource :: forall a. a -> Action (Resource a)

Ми хочемо домогтися того, щоб ці ресурси використовувались лише там, Actionде вони були створені, а не поділялися між різними діями або різними виконанням однієї і тієї ж дії, щоб дії були детермінованими та повторюваними.

Ми можемо використовувати типи вищого рейтингу, щоб досягти цього, додавши параметр Sдо Resourceі Actionтипів, який є абсолютно абстрактним - він представляє "область" Action. Тепер наші підписи:

T run<T>(<S> Action<S, T>)
Action<S, Resource<S, T>> newResource<T>(T)

runAction :: forall a. (forall s. Action s a) -> a
newResource :: forall s a. a -> Action s (Resource s a)

Тепер, коли ми даємо runActionанну Action<S, T>, ми впевнені, що оскільки параметр "область" Sє повністю поліморфним, він не може вийти з тіла runAction- тому будь-яке значення типу, яке використовує Sтаке, як Resource<S, int>не може вийти!

(У Haskell це відоме як STмонада, де runActionназивається runST, Resourceназивається STRef, і newResourceназивається newSTRef.)


STМонада є дуже цікавий приклад. Чи можете ви навести ще кілька прикладів, коли поліморфізм вищого рангу був би корисним?
GregRos

@GregRos: Це також зручно з екзистенціалами. У Haxl у нас було подібне існування data Fetch d = forall a. Fetch (d a) (MVar a), яке представляє собою пару запитів до джерела даних dта слот, в якому зберігати результат. Результат і слот повинні мати відповідні типи, але цей тип прихований, тому ви можете мати неоднорідний список запитів до одного джерела даних. Тепер ви можете використовувати більш високий ранг поліморфізм , щоб написати функцію , яка витягує всі запити, цю функцію , яка витягує один: fetch :: (forall a. d a -> IO a) -> [Fetch d] -> IO ().
Джон Перді

8

Поліморфізм вищого рангу надзвичайно корисний. У System F (основна мова набраних вами FP мов, які ви знайомі), це важливо для дозволу "набраних кодувань церкви", що насправді є системою F, що програмує. Без них система F абсолютно марна.

У системі F ми визначаємо числа як

Nat = forall c. (c -> c) -> c -> c

Доповнення має тип

plus : Nat -> Nat -> Nat
plus l r = Λ t. λ (s : t -> t). λ (z : t). l s (r s z)

який є вищим типом ( forall c.відображається всередині цих стрілок).

Це з’являється і в інших місцях. Наприклад, якщо ви хочете вказати, що обчислення є належним стилем проходження продовження (google "кодексності haskell"), ви маєте право це як

type CPSed A = forall c. (A -> c) -> c

Навіть говорити про незаселений тип у Системі F вимагає поліморфізму вищого рангу

type Void = forall a. a 

Довге і коротке цього, написання функції в системі чистого типу (System F, CoC) вимагає поліморфізму вищого рангу, якщо ми хочемо мати справу з будь-якими цікавими даними.

Зокрема, у System F ці кодування повинні бути "непередбачуваними". Це означає, що визначається forall a.кількісно щодо абсолютно всіх типів . Це критично включає той самий тип, який ми визначаємо. В forall a. aтому, що aнасправді могло б forall a. aзнову стояти ! У мовах, таких як ML, це не так, вони вважаються "предикативними", оскільки змінна типу кількісно визначається лише набором типів без кількісних показників (називаються монотипами). Наше визначення plusнеобхідного impredicativity, а тому , що ми створення екземпляра cв l : Natбути Nat!

Нарешті, я хотів би зазначити одну з останніх причин, коли ви хотіли б як непередбачуваність, так і поліморфізм вищого рангу навіть у мові з довільно рекурсивними типами (на відміну від системи F). У Haskell є монада для ефектів, яка називається "монада потоку стану". Ідея полягає в тому, що монада потоку стану дозволяє мутувати речі, але вимагає уникати цього, щоб ваш результат не залежав від нічого змінного. Це означає, що обчислення ST помітно чисті. Для виконання цієї вимоги ми використовуємо поліморфізм вищого рангу

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

Тут, гарантуючи, що aце пов'язано за межами сфери, де ми вводимо s, ми знаємо, що це aозначає добре сформований тип, на який не покладаються s. Ми використовуємо sдля парамеритизації всіх змінних речей у цій конкретній нитці стану, щоб ми знали, що aце незалежно від змінних речей, і, таким чином, ніщо не виходить із рамки цього STобчислення! Чудовий приклад використання типів для виключення неправильно сформованих програм.

До речі, якщо вам цікаво вивчити теорію типів, я б запропонував інвестувати в хорошу книгу чи дві. Важко вивчити цей матеріал шматочками та шматочками. Я б запропонував одну з книг Пірса або Харпера про теорію ФЛ взагалі (і деякі елементи теорії типів). Книга "Розширені теми у видах та мовах програмування" також охоплює велику кількість теорії типів. Нарешті, "Програмування в теорії типів Мартіна Лофа" - це дуже вдале виклад теорії інтенсивного типу, який Мартін Лоф окреслив.


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