Чому б не набрати залежно?


161

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

В основному я хотів би знати дві речі. Перший, досить просто, що це «будучи залежним від типизированного мови» на насправді означає ? (Сподіваюся, не надто технічно з цим.)

Друге питання - який недолік? Я маю на увазі, люди знають, що ми прямуємо так, тому має бути певна перевага в цьому. І все-таки, ми ще там не є, тому повинен бути якийсь недолік, який зупиняє людей, що проходять увесь шлях. У мене складається враження, що проблема полягає в стрімкому збільшенні складності. Але насправді не розуміючи, що таке типізація, я точно не знаю.

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


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

27
@GManNickG: Перевірка типу цілком можлива. Висновок типу - інша справа, але знову-таки різні розширення GHC давно відмовилися від думки про те, що слід зробити можливість зробити висновок про всі типи.
CA McCann

7
Якщо я правильно розумію, недоліком є ​​те, що робити правильне введення тексту (наприклад, таким чином, як придатним і обґрунтованим) важко , і ми ще не знаємо, як ще.
наступає штурм

1
@CAMcCann: Так, моя помилка.
GManNickG

4
Я не думаю, що хтось вказував на один великий прагматичний недолік: написання доказів того, що весь ваш код правильний, є досить шалено втомливим. Оскільки ви не можете автоматично робити умовиводи (відповідає теоремі, що підтверджує логіку "потужної потужності"), вам потрібно писати анотації до вашої програми у вигляді доказів. Це очевидно стає досадно і важко зробити через деякий час, особливо для більш досконалої монадійної магії, яку зазвичай роблять люди в Хаскеллі. Найближчим, до якого ми приїжджаємо в ці дні, є мови, які роблять більшу частину цього для нас або дають нам гарний набір примітивів.
Крістофер Мічінський

Відповіді:


21

Залежна типізація - це лише уніфікація рівнів значень та типів, тому ви можете параметризувати значення за типами (вже можна з класами типів та параметричним поліморфізмом у Haskell), а також можете параметризувати типи за значеннями (це, власне кажучи, можливо ще не в Haskell , хоча DataKindsстає дуже близько).

Редагувати: Мабуть, з цього моменту я помилявся (див. Коментар @ pigworker). Решту цього я збережу як запис про міфів, яких я годував. : P


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

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


46
Те, що ви говорите, майже повністю помилкове. Я не зовсім звинувачую вас: він повторює стандартні міфи як факт. Мова Едвіна Брейді, Ідріс, виконує стирання типу (оскільки поведінка під час виконання не залежить від типів) і генерує досить стандартне суперкомбінаторне кодування, підняте лямбда-кодом, з якого генерується код за допомогою технічних запасів G-machine.
pigworker

3
Як зауваження, хтось нещодавно вказав мені на цей документ . З того, що я можу сказати, це зробило б Haskell залежною від роду (тобто мова рівня типу буде залежно від типу), що є настільки ж близьким, як я можу побачити, як ми скоро потрапимо в будь-який час.
Полум’я Птарієна

8
Так, цей папір проходить більшу частину шляху до того, щоб показати, як зробити типи залежними від матеріалів на рівні типу (та усунути розрізнення типу / типу). Імовірне подальше спостереження, яке вже обговорюється, полягає в тому, щоб дозволити фактично залежні типи функцій, але обмежити свої аргументи фрагментом мови, який може існувати як у значущому, так і в типному шарах (тепер нетривіальний завдяки просуванню типу даних). Це позбавило б потреби в одинарній конструкції, яка наразі робить "підробку" складнішою, ніж бажаною. Ми наближаємось до реального.
pigworker

13
Є багато прагматичних запитань, які можуть переоснастити залежні типи до Haskell. Як тільки ми отримаємо цю обмежену форму залежного функціонального простору, ми все ще стикаємося з питанням, як збільшити фрагмент мови значень, дозволений на рівні типу, і якою має бути його рівняльна теорія (як ми хочемо, 2 + 2 до бути 4, і таке). Існує безліч чудернацьких проблем (наприклад, внизу), які з нуля вводять залежно від мовного дизайну далеко від руху.
pigworker

2
@pigworker Чи є загальна чиста підмножина Haskell? Якщо так, чи не могли ми просто використати це для "фрагмента мови, який може існувати як у значущому, так і в типному шарах"? Якщо ні, то що потрібно для його виготовлення?
Полум’я Птарієна

223

Залежно набрав Haskell, зараз?

Хаскелл - це в деякій мірі залежно набрана мова. Існує поняття даних на рівні типу, тепер більш розумно набране завдяки DataKinds, і є деякі засоби ( GADTs) дати представлення даних про рівень виконання даних про рівень часу. Отже, значення матеріалів, які виконуються під час виконання, ефективно відображаються у типах. Це означає, що мова має бути залежною від типу.

Прості типи даних просуваються до рівня виду, так що значення, які вони містять, можна використовувати в типах. Звідси архетипний приклад

data Nat = Z | S Nat

data Vec :: Nat -> * -> * where
  VNil   :: Vec Z x
  VCons  :: x -> Vec n x -> Vec (S n) x

стає можливим, а разом з ним і такими визначеннями, як

vApply :: Vec n (s -> t) -> Vec n s -> Vec n t
vApply VNil         VNil         = VNil
vApply (VCons f fs) (VCons s ss) = VCons (f s) (vApply fs ss)

що приємно. Зауважте, що довжина n- це чисто статична річ у цій функції, забезпечуючи те, що вхідні та вихідні вектори мають однакову довжину, хоча ця довжина не грає жодної ролі у виконанні vApply. Навпаки, це набагато складніше (тобто неможливо) реалізувати функцію , яка робить nкопії даність x(що було б , pureщоб vApply«s <*>)

vReplicate :: x -> Vec n x

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

data Natty :: Nat -> * where
  Zy :: Natty Z
  Sy :: Natty n -> Natty (S n)

Для будь-якого рекламного типу ми можемо побудувати сімейство однотонних, індексоване над промоційним типом, населене дублікатами його значень під час виконання. Natty n- це тип копій, що виконуються під час виконання, на рівні типу n :: Nat. Зараз ми можемо писати

vReplicate :: Natty n -> x -> Vec n x
vReplicate Zy     x = VNil
vReplicate (Sy n) x = VCons x (vReplicate n x)

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

Що неприємно? Що не вистачає?

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

class Nattily (n :: Nat) where
  natty :: Natty n
instance Nattily Z where
  natty = Zy
instance Nattily n => Nattily (S n) where
  natty = Sy natty

дозволяючи нам писати, скажімо,

instance Nattily n => Applicative (Vec n) where
  pure = vReplicate natty
  (<*>) = vApply

Це працює, але це означає, що наш оригінальний Natтип породив три екземпляри: вид, сімейство одиноких та клас одиночки. У нас є досить незграбний процес обміну явними Natty nзначеннями та Nattily nсловниками. Більше того, Nattyце не так Nat: ми маємо якусь залежність від значень часу виконання, але не від типу, про який ми вперше думали. Жодна повністю набрана мова не ускладнює залежні типи!

Тим часом, хоча Natможна просунути, Vecне можна. Не можна індексувати за індексованим типом. Повна мова залежно набраних мов не накладає такого обмеження, і в моїй кар’єрі як залежно набраний показ я навчився включати в свої розмови приклади двошарової індексації, просто щоб навчити людей, які зробили одношарову індексацію важко, але можливо не очікувати, що я складусь як будинок карт. В чому проблема? Рівність. GADT працюють, перекладаючи обмеження, які ви досягаєте неявно, коли ви надаєте конструктору певний тип повернення в явні рівняння. Подобається це.

data Vec (n :: Nat) (x :: *)
  = n ~ Z => VNil
  | forall m. n ~ S m => VCons x (Vec m x)

У кожному з наших двох рівнянь обидві сторони мають вид Nat.

Тепер спробуйте той же переклад для чогось, що індексується над векторами.

data InVec :: x -> Vec n x -> * where
  Here :: InVec z (VCons z zs)
  After :: InVec z ys -> InVec z (VCons y ys)

стає

data InVec (a :: x) (as :: Vec n x)
  = forall m z (zs :: Vec x m). (n ~ S m, as ~ VCons z zs) => Here
  | forall m y z (ys :: Vec x m). (n ~ S m, as ~ VCons y ys) => After (InVec z ys)

і тепер ми формуємо рівнянні обмеження між as :: Vec n xі VCons z zs :: Vec (S m) xтам, де обидві сторони мають синтаксично чіткі (але, можливо, рівні) види. Ядро GHC наразі не обладнане для такої концепції!

Чого ще не вистачає? Ну, більшість Haskell відсутня на рівні типу. Мова термінів, які ви можете рекламувати, має лише змінні та не-GADT конструктори. Після того, як у вас є ці type familyпристрої, ви зможете писати програми на рівні типу: деякі з них можуть бути цілком подібними до функцій, які ви могли б вважати написанням на рівні терміна (наприклад, оснащення Natдодатком, щоб ви могли дати хороший тип для додавання Vec) , але це просто збіг!

Ще одна річ, на практиці відсутня - це бібліотека, яка використовує наші нові здібності для індексації типів за значеннями. Що робити Functor та Monadстати у цьому відважному новому світі? Я думаю про це, але є ще багато чого зробити.

Запуск програм типу рівня

Haskell, як і більшість залежних мов програмування, має дві оперативні семантики. Ось так працює система запуску програм (лише закриті вирази, після стирання типу, сильно оптимізована), а потім є спосіб, за допомогою якого програма-контролер запускає програми (ваші сімейства типів, ваш "клас типу Prolog", з відкритими виразами). Для Haskell ви зазвичай не змішуєте їх, оскільки програми, що виконуються, є різними мовами. Мови залежно набраних типів мають окремі моделі виконання та статичні варіанти виконання для однієї мови програм, але не хвилюйтесь, модель запуску все ще дозволяє вводити стирання і, дійсно, доказ стирання: саме це видобуток Coqмеханізм дає вам; це принаймні те, що робить компілятор Едвіна Брейді (хоча Едвін стирає непотрібно дублювані значення, а також типи та докази). Розрізнення фаз більше не може бути розрізненням синтаксичної категорії , але воно живе і здорово.

Залежно набрані мови, будучи тотальними, дозволяють шпигуню запускати програми, вільні від страху нічого гіршого, ніж довгого очікування. Оскільки Haskell набирає більш залежний характер, ми стикаємося з питанням, якою повинна бути його статична модель виконання? Одним із підходів може бути обмеження статичного виконання загальними функціями, що дасть нам таку ж свободу запуску, але може змусити нас робити розрізнення (принаймні для коду рівня типу) між даними та кодами, щоб ми могли сказати, чи потрібно примусове припинення або продуктивність. Але це не єдиний підхід. Ми вільні вибирати набагато слабку модель виконання, яка не хоче запускати програми, ціною створення меншої кількості рівнянь виходить лише шляхом обчислення. По суті, саме це і робить GHC. Правила набору тексту для ядра GHC не згадують про запуск програми, але лише для перевірки доказів рівнянь. При перекладі на ядро ​​вирішувач обмежень GHC намагається запустити ваші програми на рівні типу, створюючи трохи сріблястого сліду доказів того, що даний вираз дорівнює його нормальній формі. Цей метод отримання доказів є трохи непередбачуваним і неминуче незавершеним: наприклад, бореться із сором'язливою страшною рекурсією, наприклад, і це, мабуть, мудро. Одне, про що нам не потрібно хвилюватися, - це виконання IO обчислень у контролері типу: пам’ятайте, що шпигун машин не повинен надавати launchMissilesтого ж значення, що і система виконання часу!

Культура Хіндлі-Мілнера

Система типу Хіндлі-Мілнера досягає по-справжньому приголомшливого збігу чотирьох чітких розрізнень, з нещасливим культурним побічним ефектом, що багато людей не бачать розрізнення між відмінностями і вважають, що збіг неминучий! Про що я говорю?

  • терміни проти типів
  • явно написані речі проти неявно написані речі
  • присутність під час запуску проти стирання перед часом виконання
  • не залежна абстракція від залежної кількісної оцінки

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

Вам не доведеться занадто віддалятися від ванілі Хіндлі-Мілнер, перш ніж ці відмінності вийдуть у відповідність, і це не погано . Для початку ми можемо мати більше цікавих типів, якщо ми готові написати їх у кількох місцях. Тим часом нам не потрібно писати словники класу типів, коли ми використовуємо перевантажені функції, але ці словники, безумовно, присутні (або вбудовані) під час виконання. У залежно введених мовах ми очікуємо, що під час виконання буде видалено більше, ніж просто типи, але (як це стосується класів типів), що деякі неявно виведені значення не будуть стерті. Наприклад, vReplicateчисловий аргумент часто можна порівняти з типу потрібного вектора, але нам все одно потрібно знати це під час виконання.

Який вибір мовного дизайну слід переглянути, оскільки ці збіги вже не мають значення? Наприклад, чи правильно, що Haskell не забезпечує способу forall x. tявного визначення кількісного показника? Якщо машинка для введення тексту не може здогадатися x, уніфікуючи t, у нас немає іншого способу сказати, щоx має бути.

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

Наступні кроки щодо Haskell

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

Ми повинні спростити та узагальнити діючу систему обмежень, дозволяючи гетерогенні рівняння, a ~ bде види aта bне є синтаксично однаковими (але можуть бути доведені рівними). Це стара методика (в моїй тезі минулого століття), яка набагато легше справляється із залежністю. Ми зможемо висловити обмеження на вирази в GADT, і таким чином зняти обмеження щодо того, що можна рекламувати.

Ми повинні виключити необхідність будівництва одноплідних шляхом введення залежного типу функції, pi x :: s -> t. Функція з таким типом може бути явно застосована до будь-якого виразу типу, sяке живе в перетині мов типу та терміна (так, змінні, конструктори, з якими більше пізніше). Відповідна лямбда та додаток не буде стерто під час виконання, тому ми зможемо написати

vReplicate :: pi n :: Nat -> x -> Vec n x
vReplicate Z     x = VNil
vReplicate (S n) x = VCons x (vReplicate n x)

без заміни Natна Natty. Домен piможе бути будь-якого рекламного типу, тому, якщо можна просувати GADT, ми можемо записувати залежні послідовності кількісних показників (або "телескопи", як їх називав Де Брюййн)

pi n :: Nat -> pi xs :: Vec n x -> ...

будь-якої довжини нам потрібно.

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

Занадто складно?

Залежні типи нервують багатьох людей. Вони нервують мене, але мені подобається нервувати, або принаймні мені важко не нервувати. Але це не допомагає, що навколо цієї теми досить туман невігластва. Дещо це пов’язано з тим, що всім нам ще багато чому навчитися. Але, як відомо, прихильники менш радикальних підходів закладають страх перед залежними типами, не переконуючись, що факти цілком пов'язані з ними. Я не буду називати імена. Ці міфи "нерозбірливі перевірки типу", "Тюрінг незавершений", "відсутність фазового розрізнення", "відсутність стирання типу", "докази скрізь" тощо.

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

Бо, як і при будь-якому збільшенні артикуляції, ми можемо вільно говорити про недобрі речі, а також про справедливі. Наприклад, існує безліч крихких способів визначення дерев бінарного пошуку, але це не означає, що це не гарний спосіб . Важливо не припускати, що поганого досвіду не можна покращити, навіть якщо воно заполонить его, щоб визнати це. Розробка залежних визначень - це нова майстерність, яка вимагає навчання, а те, що програміст Haskell, не робить вас автоматично експертом! І навіть якщо деякі програми є невірними, чому б ви заперечували іншим свободу бути справедливою?

Навіщо все-таки турбуватися з Haskell?

Мені дуже подобаються залежні типи, але більшість моїх хакерських проектів все ще перебувають у Haskell. Чому? У Haskell є типи класів. У Haskell є корисні бібліотеки. Haskell має працездатний (хоча і далеко не ідеальний) режим програмування з ефектами. Haskell має промисловий компілятор міцності. Мови залежно введеного типу знаходяться на набагато більш ранній стадії у зростанні спільноти та інфраструктури, але ми потрапимо туди, з реальним поколінням зрушень у тому, що можливо, наприклад, шляхом метапрограмування та типових даних даних. Але вам просто потрібно озирнутися, що роблять люди в результаті кроків Хаскелла до залежних типів, щоб побачити, що є багато користі, яка також може бути отримана шляхом висування сучасного покоління мов.


6
Мені справді все одно про дані DataKinds. В основному тому , що я хочу зробити що - щось на зразок цього: fmap read getLine >>= \n -> vReplicate n 0. Як зазначаєте, Nattyє шляхи від цього. Крім того, vReplicate повинен бути перекладений у фактичний масив пам'яті, щось на зразок newtype SVector n x = SVector (Data.Vector.Vector x), де nмає вид Nat(або подібний). Можливо, ще один демонстраційний пункт для "залежно типового шоу"?
Джон Л

7
Чи можете ви сказати, що ви маєте на увазі для ідеального лікування програмування з ефектами?
Стівен Шоу

6
Дякуємо за чудове написання. Я хотів би побачити кілька прикладів залежно введеного коду, де деякі дані походять за межами програми (наприклад, зчитуються з файлу), щоб відчути, як виглядатиме просування значень для типів у таких умовах. У мене таке відчуття, що всі приклади включають вектори (реалізовані як списки) зі статистично відомими розмірами.
tibbe

4
@pigworker Ви вважаєте міф "без фазового розрізнення" (інші, з якими я згоден, - це міфи). Але ви цього не демонтували в документах і розмовах, яких я бачив, а тим часом інша людина, яку я поважаю, говорить мені: "Теорія залежного типу відрізняється від типового компілятора, оскільки ми не можемо значимо розділити фази перевірки, компіляції та виконання. " (див. останнє повідомлення Андрія від 8 листопада 2012 р.) З мого досвіду "підробляючи це", ми іноді принаймні розмиваємо розрізнення фаз, хоча й не потрібно його стирати. Чи можете ви розширити, якщо не тут, то в іншому місці з цього питання?
sclv

4
@sclv Моя робота не була особливо орієнтована на міф "без фазового розрізнення", але на інші ". Я рекомендую відхилити "Фазові розрізнення у складі епіграми" Джеймса Маккінна та Едвіна Брейді як гарне місце для початку. Але дивіться також набагато старіші роботи з вилучення програм у Coq. Оцінка відкритих термінів, виконана інструментом перевірки набору даних, повністю відокремлена від виконання шляхом вилучення в ML, і зрозуміло, що вилучення викреслює види та докази.
pigworker

20

Джон - це ще одна поширена помилка щодо залежних типів: вони не працюють, коли дані доступні лише під час виконання. Ось як можна зробити приклад getLine:

data Some :: (k -> *) -> * where
  Like :: p x -> Some p

fromInt :: Int -> Some Natty
fromInt 0 = Like Zy
fromInt n = case fromInt (n - 1) of
  Like n -> Like (Sy n)

withZeroes :: (forall n. Vec n Int -> IO a) -> IO a
withZeroes k = do
  Like n <- fmap (fromInt . read) getLine
  k (vReplicate n 0)

*Main> withZeroes print
5
VCons 0 (VCons 0 (VCons 0 (VCons 0 (VCons 0 VNil))))

Редагувати: Гм, це мав стати коментарем до відповіді пороботників. Я явно не в стані SO.


Ваше перше речення здається трохи дивним; Я б сказав , що точка залежних типів є те , що вони роблять роботу , коли дані доступні тільки під час виконання. Однак ця методика в стилі CPS не однакова. Припустимо, у вас є функція Vec Zy -> IO String. Ви не можете використовувати його withZeroes, оскільки тип Zyне може бути уніфікований для forall n. Можливо, ви можете обійти це для одного або двох особливих випадків, але це швидко виходить з рук.
Джон Л

Ключовим фактором, коли ви приймаєте просто набране значення (наприклад, String від getLine) і перетворюєте його на щось із більш сильним типом (як Natty n нагорі), - це те, що ви повинні переконати перевіряючого типу, що ви робите необхідні динамічні перевірки. У вашому прикладі ви читаєте довільне число, так що це forall nмає сенс. Точніші обмеження можуть бути реалізовані таким же чином. Чи є у вас кращий приклад, ніж Vec Zy(програмі все одно потрібно обробляти користувач, який вводить 5, а не 0)?
ulfnorell

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

1
ulfnorell: Вибачте, я не зрозумів. Припустимо, у вас є одна функція, яка працюватиме з Vec Zy -> IO Stringіншою Vec n -> IO String, і перша, і ви хочете використовувати першу, лише якщо тип відповідає. Так, це можливо, але механізми його включення незграбні. І це дуже проста логіка; якщо у вас складніша логіка - це гірше. Також вам може знадобитися переписати багато коду в CPS. І ви все ще не маєте виразу на рівні типу, який залежить від терміна на рівні значення
Джон L

Ах, я бачу, що ти кажеш. Це те, для чого є Натті, як у vReplicate, де ми робимо різні речі залежно від n. Дійсно, це може стати трохи незграбним. Альтернатива стилю КПСА є роботою з фасованими екзістенціалах: zeroes :: IO (Some (Flip Vec Int)).
ulfnorell

19

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

Щодо "чому ні?" питання, я думаю, є кілька моментів. Перший момент полягає в тому, що хоча базове поняття за залежними типами є простим (дозволяють типам залежати від значень), наслідки цього базового поняття є тонкими і глибокими. Наприклад, відмінність між значеннями і типами все ще живе і здорово; але обговорення різниці між ними стає далекобільш нюансований, ніж у Йіндрі - Мілнер або Система F. Певною мірою це пов'язано з тим, що залежні типи принципово важкі (наприклад, логіка першого порядку не визначитися). Але я думаю, що більша проблема полягає в тому, що нам не вистачає хорошого словника для фіксації та пояснення того, що відбувається. Оскільки все більше і більше людей дізнаються про залежні типи, ми розвиватимемо кращий словниковий запас, і тому речі стануть простішими для розуміння, навіть якщо основні проблеми ще важкі.

Другий момент пов'язаний з тим, що є Haskell ростедо залежних типів. Оскільки ми досягаємо поступового прогресу до досягнення цієї мети, але насправді не досягаючи цього, ми застрягли з мовою, яка має поступові виправлення поверх інкрементних патчів. Так само було і в інших мовах, коли нові ідеї стали популярними. Java не використовувала (параметричний) поліморфізм; і коли вони, нарешті, додали його, це було, очевидно, поступовим покращенням із деякими витоками абстракції та калікою. Виявляється, змішування підтипів і поліморфізму за своєю суттю важко; але це не причина, чому Java Generics працює так, як вони роблять. Вони працюють так, як вони роблять через обмеження, щоб бути поступовим вдосконаленням для старих версій Java. Дітто, для подальшого повернення в той день, коли був винайдений ООП і люди почали писати "об'єктивно" C (не плутати з Objective-C) і т. Д. Пам'ятайте, що C ++ розпочався під виглядом суворої сукупності C. Для додавання нових парадигм завжди потрібно визначати мову заново або інакше закінчувати якийсь складний безлад. Моя думка у всьому цьому полягає в тому, що для додавання справжніх залежних типів до Haskell буде потрібно певна кількість видобутку та перебудови мови --- якщо ми будемо робити це правильно. Але насправді важко здійснити такий вид капітального ремонту, тоді як поступовий прогрес, який ми досягли, здається дешевшим у короткостроковій перспективі. Дійсно, не так вже й багато людей, які хакують GHC, але є достатня кількість застарілого коду для збереження життя. Це є причиною того, що існує стільки мов спінофу, як DDC, Cayenne, Idris тощо. C ++ розпочався під виглядом суворої сукупності C. Додавання нових парадигм завжди вимагає визначення мови заново, інакше закінчується складним безладом. Моя думка у всьому цьому полягає в тому, що для додавання справжніх залежних типів до Haskell буде потрібно певна кількість видобутку та перебудови мови --- якщо ми будемо робити це правильно. Але насправді важко здійснити такий вид капітального ремонту, тоді як поступовий прогрес, який ми досягли, здається дешевшим у короткостроковій перспективі. Дійсно, не так вже й багато людей, які хакують GHC, але є достатня кількість застарілого коду для збереження життя. Це є причиною того, що існує стільки мов спінофу, як DDC, Cayenne, Idris тощо. C ++ розпочався під виглядом суворої сукупності C. Додавання нових парадигм завжди вимагає визначення мови заново, інакше закінчується складним безладом. Моя думка у всьому цьому полягає в тому, що для додавання справжніх залежних типів до Haskell буде потрібно певна кількість видобутку та перебудови мови --- якщо ми будемо робити це правильно. Але насправді важко здійснити такий вид капітального ремонту, тоді як поступовий прогрес, який ми досягли, здається дешевшим у короткостроковій перспективі. Дійсно, не так вже й багато людей, які хакують GHC, але є достатня кількість застарілого коду для збереження життя. Це є причиною того, що існує стільки мов спінофу, як DDC, Cayenne, Idris тощо. інакше закінчується якийсь складний безлад. Моя думка у всьому цьому полягає в тому, що для додавання справжніх залежних типів до Haskell буде потрібно певна кількість видобутку та перебудови мови --- якщо ми будемо робити це правильно. Але насправді важко здійснити такий вид капітального ремонту, тоді як поступовий прогрес, який ми досягли, здається дешевшим у короткостроковій перспективі. Дійсно, не так вже й багато людей, які хакують GHC, але є достатня кількість застарілого коду для збереження життя. Це є причиною того, що існує стільки мов спінофу, як DDC, Cayenne, Idris тощо. інакше закінчується якийсь складний безлад. Моя думка у всьому цьому полягає в тому, що для додавання справжніх залежних типів до Haskell буде потрібно певна кількість видобутку та перебудови мови --- якщо ми будемо робити це правильно. Але насправді важко здійснити такий вид капітального ремонту, тоді як поступовий прогрес, який ми досягли, здається дешевшим у короткостроковій перспективі. Дійсно, не так вже й багато людей, які хакують GHC, але є достатня кількість застарілого коду для збереження життя. Це є причиною того, що існує стільки мов спінофу, як DDC, Cayenne, Idris тощо. s насправді важко здійснити такий вид капітального ремонту, тоді як поступовий прогрес, який ми досягли, здається дешевшим у короткостроковій перспективі. Дійсно, не так вже й багато людей, які хакують GHC, але є достатня кількість застарілого коду для збереження життя. Це є причиною того, що існує стільки мов спінофу, як DDC, Cayenne, Idris тощо. s насправді важко здійснити такий вид капітального ремонту, тоді як поступовий прогрес, який ми досягли, здається дешевшим у короткостроковій перспективі. Дійсно, не так вже й багато людей, які хакують GHC, але є достатня кількість застарілого коду для збереження життя. Це є причиною того, що існує стільки мов спінофу, як DDC, Cayenne, Idris тощо.

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