Які пункти суворості Хаскелла?


90

Всі ми знаємо (або повинні знати), що Хаскелл ледачий за замовчуванням. Ніщо не оцінюється, поки не повинно бути оцінено. Тож коли треба щось оцінювати? Є моменти, коли Хаскелл повинен бути суворим. Я називаю це "пунктами суворості", хоча цей конкретний термін не настільки поширений, як я думав. За моїми словами:

Зменшення (або оцінка) у Haskell відбувається лише в точках суворості.

Таким чином, питання: які, саме , є точками Строгість Хаскеля? Моя інтуїція говорить , що main, seq/ чубчик модель, зіставлення з зразком, і будь-який IOдію , що здійснюється з допомогою mainє первинними точками Строгості, але я не знаю , чому я це знаю.

(Крім того , якщо вони не називаються «точка строгості», що є вони називаються?)

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


Редагувати: додаткові думки щодо цього питання.

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

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

Тепер ви спробуєте: обговорити seqта узгодження шаблонів у цих термінах. Поясніть нюанси застосування функції: наскільки вона сувора? Як це ні? Що про deepseq? letі caseзаяви? unsafePerformIO? Debug.Trace? Визначення верхнього рівня? Строгі типи даних? Візерунки вибуху? І т. Д. Скільки з цих елементів можна описати з точки зору просто наступних чи збігів шаблонів?


10
Ваш інтуїтивний список, мабуть, не дуже ортогональний. Я підозрюю, що seqі узгодження зразків є достатніми, а решта визначається з точки зору цих. Я думаю, що узгодження шаблонів забезпечує IO, наприклад, суворість дій у хребті .
CA McCann

Примітивні, такі як +вбудовані числові типи, також вимагають суворості, і я припускаю, що те саме стосується чистих викликів FFI.
hammar

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

@ChrisSmith Я не намагаюся переплутати ці два різні випадки; якщо що, я прошу додаткових роз'яснень щодо їх взаємодії. Суворість буває якось, і обидва випадки є важливими, хоча і різними, частинами суворості, що «відбуваються». (та @ monadic: ಠ_ಠ)
Ден Бертон,

Якщо ви хочете / потребуєте місця для обговорення аспектів цього питання, не намагаючись дати повну відповідь, дозвольте мені запропонувати використати коментарі до мого / r / haskell повідомлення для цього питання
Ден Бертон,

Відповіді:


46

Хорошим місцем для початку є розуміння цієї статті: „Природна семантика для ледачої еволюції” (Launchbury). Це покаже вам, коли вирази обчислюються для невеликої мови, подібної до Ядра GHC. Тоді залишається питання, як зіставити повний Haskell з Core, і більша частина цього перекладу дається в самому звіті Haskell. У GHC ми називаємо цей процес "знежиренням", оскільки він виводить синтаксичний цукор.

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

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


18
Мені завжди було кумедно, що у тому, що GHC називає "знежиренням", синтаксичний цукор, що видаляється, включає фактичний синтаксис самої мови Хаскелла ... маючи на увазі, може здатися, що GHC насправді є оптимізаційним компілятором для GHC Основна мова, яка, до речі, також включає дуже складний інтерфейс для перекладу Haskell в Core. :]
CA McCann

Системи типів не обробляються точно, хоча ... особливо, але не лише щодо перекладу типів класів у явні словники, як я пам’ятаю. І, як я розумію, усі останні новини про TF / GADT ще більше розширили цей розрив.
sclv


20

Я б, напевно, переробив це запитання так: За яких обставин Хаскелл оцінить вираз? (Можливо, вирішити "слабку голову в нормальній формі".)

Для першого наближення ми можемо вказати це наступним чином:

  • Виконуючи дії введення-виведення, буде оцінено будь-які вирази, які вони “потребують”. (Отже, вам потрібно знати, чи виконується дія вводу-виводу, наприклад, це ім’я main, або воно викликається з main І вам потрібно знати, для чого потрібна дія.)
  • Вираз, який оцінюється (привіт, це рекурсивне визначення!), Обчислить усі вирази, які йому потрібні.

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

Надання всіх деталей конкретно - велике завдання, оскільки Haskell - велика мова. Це також досить тонко, тому що одночасний Хаскелл може оцінювати речі спекулятивно, навіть якщо ми в підсумку не використовуємо результат у підсумку: це третя порода речей, які викликають оцінку. Друга категорія досить добре вивчена: ви хочете поглянути на суворість задіяних функцій. Перша категорія теж можна розглядати як свого роду «суворість», хоча це трохи хитромудрий , тому що evaluate xі seq x $ return ()насправді це різні речі! Ви можете правильно з ним поводитися, якщо ви надаєте якусь семантику монаді IO (явна передача RealWorld#маркера працює для простих випадків), але я не знаю, чи існує назва для такого роду стратифікованого аналізу строгості загалом.


17

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

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

...

Програмісти також можуть використовувати seqпримітив, щоб змусити вираз оцінювати незалежно від того, чи буде колись використаний результат.

$!визначається в термінах seq.

- Ледачий проти несуворого .

Отже, ваше мислення щодо !/ $!і seqє по суті правильним, але узгодження шаблонів підпорядковується більш тонким правилам. ~Звичайно, ви завжди можете скористатися форсуванням лінивих збігів. Цікавий момент із тієї самої статті:

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

Давайте продовжимо кролицьку нору і подивимось на документи щодо оптимізації, проведеної GHC:

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

- Оптимізація GHC: Аналіз суворості .

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

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

- Wikibooks Haskell: Лінощі

(Термін є в нормальній формі голови, якщо в положенні голови 1 немає бета-редексу . Редекс є головним редексом, якщо йому передують лише лямбда-абстрактори нередексів 2 ). Отже, коли ви починаєте змушувати глузника, ви працюєте в WHNF; коли більше не залишається силових ударів, ти в нормальній формі. Ще один цікавий момент:

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

Що, природно, означає, що, дійсно, будь-яка IOдія, яка виконується, main робить оцінку сили, що повинно бути очевидним, враховуючи, що програми Haskell насправді роблять щось. Все, що потрібно пройти через послідовність, визначену в, mainмає бути в нормальній формі, і тому підлягає суворій оцінці.

CA McCann зрозумів це прямо в коментарях: однак єдине, що є особливим, mainце те, що mainвизначено як особливе; узгодження шаблону на конструкторі достатньо для забезпечення послідовності, накладеної IOмонадою. У цьому відношенні seqпринципово важливим є лише узгодження шаблонів.


4
Насправді цитата "якщо в якийсь момент нам потрібно було, скажімо, роздрукувати z користувачеві, нам потрібно буде його повністю оцінити", є не зовсім правильною. Він такий же суворий, як Showекземпляр для значення, яке друкується.
nominolo

10

Хаскелл - це AFAIK не суто ледача мова, а скоріше несувора мова. Це означає, що він не обов'язково оцінює терміни в останній можливий момент.

Хороше джерело для моделі "лінощів" Haskell можна знайти тут: http://en.wikibooks.org/wiki/Haskell/Laziness

В основному важливо розуміти різницю між дурнем і слабким заголовком нормальної форми WHNF.

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

Оскільки це могло б призвести до жахливого витоку простору, компілятор потім з’ясовує, як і коли оцінювати хитрощі заздалегідь, щоб заощадити місце. Потім програміст може підтримати цей процес, надаючи анотації строгості (en.wikibooks.org/wiki/Haskell/Strictness, www.haskell.org/haskellwiki/Performance/Strictness), щоб ще більше зменшити використання простору у вигляді вкладених фотографій.

Я не фахівець в оперативній семантиці haskell, тому просто залишу посилання як ресурс.

Ще деякі ресурси:

http://www.haskell.org/haskellwiki/Performance/Laziness

http://www.haskell.org/haskellwiki/Haskell/Lazy_Evaluation


6

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

Для початківця основне правило let- лінивий, caseменш ледачий.


2
Зауважте, що, хоча caseзавжди примусово оцінює в GHC Core, він не робить цього в звичайному Haskell. Наприклад, спробувати case undefined of _ -> 42.
hammar

2
caseу GHC Core оцінює свій аргумент WHNF, тоді як caseу Haskell оцінює свій аргумент стільки, скільки потрібно для вибору відповідної гілки. У прикладі Хаммара це зовсім не так, але в Росії case 1:undefined of x:y:z -> 42він оцінює глибше, ніж WHNF.
Макс.

А також case something of (y,x) -> (x,y)не потрібно взагалі оцінювати something. Це справедливо для всіх типів продукції.
Інго

@Ingo - це неправильно. somethingпотрібно було б оцінити WHNF, щоб досягти конструктора кортежу.
John L

Джон - Чому? Ми знаємо, що це повинен бути кортеж, то де сенс його оцінювати? Достатньо, якщо x та y прив'язані до коду, який обчислює кортеж і витягує відповідний слот, якщо вони коли-небудь знадобляться.
Інго

4

Це не повна відповідь, спрямована на карму, а лише частина загадки - наскільки це стосується семантики, майте на увазі, що існує безліч стратегій оцінки, які забезпечують однакову семантику. Одним хорошим прикладом тут - і проект також говорить про те, як ми зазвичай думаємо про семантику Haskell - був проект Eager Haskell, який радикально змінив стратегії оцінки, зберігаючи ту саму семантику: http://csg.csail.mit.edu/ pubs / haskell.html


2

Компілятор Глазго Хаскелл перекладає ваш код на мову, подібну до лямбда-числення, що називається core . У цій мові щось буде оцінено щоразу, коли ви caseзбігаєтесь із шаблоном за допомогою -значення. Таким чином, якщо викликана функція, буде обчислюватися крайній конструктор і лише він (якщо немає вимушених полів). Будь-що інше консервується в потік. (Подумки вводитьlet прив’язок).

Звичайно, це не зовсім те, що відбувається у справжній мові. Компілятор перетворює Haskell в Core дуже вишукано, роблячи якомога більше речей лінивими та будь-що, що завжди потрібно, ледачим. Окрім того, існують незмінні значення та кортежі, які завжди суворі.

Якщо ви намагаєтесь оцінити функцію вручну, ви можете в основному подумати:

  • Спробуйте оцінити зовнішній конструктор повернення.
  • Якщо ще щось потрібно для отримання результату (але лише якщо це дійсно потрібно), також буде оцінено. Порядок не має значення.
  • У випадку IO ви повинні оцінити результати всіх тверджень від першого до останнього. Це дещо складніше, оскільки монада IO робить деякі трюки, щоб змусити оцінку в певному порядку.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.