Чи монада IO технічно неправильна?


12

На вікі haskell є наступний приклад умовного використання монади IO (див. Тут) .

when :: Bool -> IO () -> IO ()
when condition action world =
    if condition
      then action world
      else ((), world)

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

Цей фрагмент умовно виконує дію в монаді IO. Тепер, припускаючи, що conditionце є False, дія actionніколи не повинна виконуватися. Використовуючи ліниву семантику, це справді було б так. Однак тут зазначається, що Haskell технічно говорить не суворо. Це означає, що компілятору дозволено, наприклад, попередньо запускати action worldінший потік, а пізніше викидати це обчислення, коли виявляє, що йому це не потрібно. Однак до цього моменту побічні ефекти вже відбудуться.

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

Чи означає це, що монада IO технічно помиляється, чи щось інше заважає цьому статися?


Ласкаво просимо до інформатики ! Ваше запитання тут поза темою: ми маємо справу з питаннями інформатики , а не з питаннями програмування (див. Наші FAQ ). Ваше запитання може бути тематичним щодо переповнення стека .
dkaeae

2
На мою думку, це питання інформатики, оскільки воно стосується теоретичної семантики Хаскелла, а не питання практичного програмування.
Лассе

4
Я не надто знайомий з теорією мови програмування, але думаю, що це питання тут є темою. Це може допомогти, якщо ви поясніть, що тут означає «неправильне». Яку властивість, на вашу думку, має монада IO, якої вона не повинна мати?
дискретна ящірка

1
Ця програма не надрукована. Я не впевнений, що ти насправді мав намір написати. Визначення whenможна набрати, але воно не має типу, який ви заявляєте, і я не бачу, що робить цей конкретний код цікавим.
Жиль "ТАК - перестань бути злим"

2
Ця програма взята дослівно зі сторінки Haskell-wiki, пов'язаної безпосередньо вище. Це дійсно не тип. Це пояснюється тим, що це написано в припущенні, яке IO aвизначене як RealWorld -> (a, RealWorld), щоб зробити внутрішні дані IO більш читабельними.
Лассе

Відповіді:


12

Це запропоноване «тлумачення» IOмонади. Якщо ви хочете сприймати цю "інтерпретацію" серйозно, тоді вам потрібно сприймати "RealWorld" серйозно. Незалежно від того, action worldотримано спекулятивну оцінку чи ні, actionне має жодних побічних ефектів, її ефекти, якщо такі є, обробляються шляхом повернення нового стану Всесвіту там, де ці ефекти виникли, наприклад, надісланий мережевий пакет. Однак результатом функції є ((),world)і тому новий стан Всесвіту є world. Ми не використовуємо новий всесвіт, який, можливо, ми спекулятивно оцінили на стороні. Стан Всесвіту є world.

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

«Почекай, почекай», - кажеш ти. " RealWorldце просто" маркер ". Це насправді не стан усього Всесвіту". Гаразд, тоді ця "інтерпретація" нічого не пояснює. Тим не менш, як деталь реалізації , це, як моделі GHC IO. 1 Це означає, що у нас є магічні "функції", які насправді мають побічні ефекти, і ця модель не дає ніяких вказівок щодо їх значення. І оскільки ці функції насправді мають побічні ефекти, ви викликаєте занепокоєння повністю. GHC дійсно повинен вийти зі свого шляху , щоб переконатися , що RealWorldі ці спеціальні функції не оптимізовані таким чином , щоб змінити цільове поведінка програми.

Особисто (як це, мабуть, очевидно зараз), я вважаю, що ця "миротворча" модель IOпросто марна і заплутана як педагогічний інструмент. (Чи корисно це для впровадження, я не знаю. Для GHC, я думаю, це більше історичний артефакт.)

Один з альтернативних підходів - розглядати IOяк опис запитів з обробниками відповідей. Існує кілька способів зробити це. Напевно, найбільш доступним є використання безкоштовної конструкції монади, зокрема ми можемо використовувати:

data IO a = Return a | Request OSRequest (OSResponse -> IO a)

Існує багато способів зробити це більш досконалим і мати дещо кращі властивості, але це вже вдосконалення. Для розуміння цього не потрібно глибоких філософських припущень щодо природи реальності. Все, що він стверджує, - IOце або тривіальна програма, Returnяка не робить нічого, крім повернення значення, або це запит в операційну систему з обробником для відповіді. OSRequestможе бути щось на кшталт:

data OSRequest = OpenFile FilePath | PutStr String | ...

Так само OSResponseможе бути щось на кшталт:

data OSResponse = Errno Int | OpenSucceeded Handle | ...

(Одне з удосконалень , які можна зробити, щоб зробити речі більш тіпобезопасно так , що ви знаєте , що ви не отримаєте OpenSucceededвід PutStrзапиту.) Ці моделі , IOяк описують запитів , які інтерпретуються деякої система (для «реального» IOмонади це сам час виконання Haskell), а потім, можливо, ця система викличе обробник, який ми надали у відповідь. Це, звичайно, також не вказує на те, як PutStr "hello world"слід обробляти такий запит , але він також не претендує. Зрозуміло, що це делеговано іншій системі. Ця модель також досить точна. Усі користувацькі програми в сучасних ОС повинні робити запити в ОС, щоб зробити що-небудь.

Ця модель забезпечує правильні інтуїції. Наприклад, багато початківців розглядають такі речі, як <-оператор, як "розгортання" IOабо мають (на жаль, підкріплені) погляди, що IO String, скажімо, є "контейнером", який містить " Strings" (а потім <-витягує їх). Цей погляд-відповідь робить цю перспективу явно неправильною. Немає файлової обробки всередині OpenFile "foo" (\r -> ...). Поширена аналогія, яка наголошує на цьому, полягає в тому, що в рецепті торта немає торта (або, можливо, «фактура» була б кращою в цьому випадку).

Ця модель також легко працює з одночасністю. Ми можемо легко створити конструктор для OSRequestподібних, Fork :: (OSResponse -> IO ()) -> OSRequestі тоді час виконання може переплутати запити, які виробляє цей додатковий обробник, із звичайним обробником, як це не подобається. З деякою кмітливістю ви можете використовувати цю техніку (або пов'язані з нею методи), щоб насправді моделювати речі, такі як паралельність, безпосередньо, а не просто говорити "ми робимо запит в ОС і все відбувається" Так працює IOSpecбібліотека .

1 Hugs використовував реалізацію на основі продовження, IOяка приблизно схожа на те, що я описую, хоча і з непрозорими функціями замість явного типу даних. HBC також використовувала реалізацію, що базується на продовженні, шарується над старим IO на основі запиту-відповіді. NHC (і, таким чином, YHC) використовував громовідвід, тобто приблизно, IO a = () -> aхоча це ()називалося World, але це не робить державний прохід. JHC та UHC використовували в основному той самий підхід, що і GHC.


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

Один коментар: здається, що OpenFile "foo" (\r -> ...)насправді має бути Request (OpenFile "foo") (\r -> ...)?
Лассе

@Lasse Так, це мало бути з Request. Щоб відповісти на ваше перше запитання, це IOявно не чутливо до порядку оцінювання (модульні днища), оскільки це інертне значення. Усі побічні ефекти (якщо такі є) будуть зроблені тим, що інтерпретує це значення. У whenприкладі було б неважливо, чи actionйого оцінювали, оскільки це було б просто таке значення, Request (PutStr "foo") (...)яке ми все одно не дамо тому, що інтерпретує ці запити. Це як вихідний код; не має значення, чи скорочуєте ви її жадібно чи ліниво, нічого не відбувається, поки його не дадуть перекладачеві.
Дерек Елкінс покинув SE

Ага так, я це бачу. Це дійсно розумне визначення. Спочатку я подумав, що всі побічні ефекти обов'язково повинні відбутися, коли вся програма закінчиться, тому що ви повинні побудувати структуру даних, перш ніж ви зможете її інтерпретувати. Але оскільки запит містить продовження, вам потрібно створити дані лише першими Request, щоб почати бачити побічні ефекти. Наступні побічні ефекти можуть бути створені при оцінці продовження. Розумний!
Лассе
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.