Найбільш очевидне нововведення, яке помічають люди, які не знайомі з Haskell, - це те, що існує розмежування між нечистим світом, який пов'язаний із спілкуванням із зовнішнім світом, та чистим світом обчислень та алгоритмів. Часто початківець питання : «Як я можу позбутися від IO
, тобто, зверненого IO a
в a
?» Шлях до цього - використовувати монади (або інші абстракції) для написання коду, який виконує IO та ланцюгові ефекти. Цей код збирає дані із зовнішнього світу, створює його модель, проводить деякі обчислення, можливо, використовуючи чистий код, і виводить результат.
Що стосується вищезгаданої моделі, я не бачу нічого страшного поганого в маніпулюванні графічними інтерфейсами в IO
монаді. Найбільша проблема, що виникає в цьому стилі, полягає в тому, що модулі вже не є композиційними, тобто я втрачаю більшість своїх знань про глобальний порядок виконання заяв у своїй програмі. Щоб відновити його, я повинен застосувати аналогічні міркування, як і у паралельного, обов'язкового коду GUI. Тим часом, для нечистого, не-GUI-коду, порядок виконання очевидний через визначення оператора IO
монади >==
(принаймні, поки існує лише один потік). Для чистого коду це взагалі не має значення, за винятком випадків, коли для підвищення продуктивності чи уникнення оцінок у результаті не виникає ⊥
.
Найбільша філософська різниця між консольним та графічним IO полягає в тому, що програми, що реалізують перший, написані зазвичай у синхронному стилі. Це можливо тому, що існує (залишаючи вбік сигнали та інші відкриті дескриптори файлів) лише одне джерело подій: байт-потік, який зазвичай називають stdin
. Графічні інтерфейси по суті є асинхронними, і повинні реагувати на події клавіатури та клацання миші.
Популярна філософія функціонування асинхронного IO у функціональному відношенні називається функціональним реактивним програмуванням (FRP). Останнім часом він отримав велику тягу в нечистих, нефункціональних мовах завдяки таким бібліотекам, як ReactiveX , та рамкам, як Elm. Коротше кажучи, це як перегляд елементів графічного інтерфейсу та інших речей (наприклад, файлів, годинників, тривог, клавіатури, миші) як джерел подій, званих "спостережними", які випромінюють потоки подій. Ці події об'єднуються з допомогою знайомих операторів , таких , як map
, foldl
, zip
, filter
, concat
, join
і т.д., для створення нових потоків. Це корисно, оскільки сам стан програми можна розглядати як scanl . map reactToEvents $ zipN <eventStreams>
програму, де N
дорівнює кількості спостережуваних, які коли-небудь розглядалися програмою.
Робота зі спостережуваними даними FRP дає змогу відновити сумісність, оскільки події в потоці впорядковані вчасно. Причина полягає в тому, що абстракція потоку подій дозволяє переглянути всі спостережувані дані як чорні поля. Зрештою, поєднання потоків подій за допомогою операторів повертає деякі локальні замовлення на виконання. Це змушує мене бути більш чесним щодо того, на які інваріанти покладається моя програма насправді, аналогічно тому, що всі функції в Haskell повинні бути референтно прозорими: якщо я хочу перетягувати дані з іншої частини своєї програми, я повинен бути явним оголошення оголосити відповідний тип для моїх функцій. (Монада IO, будучи специфічною для домену мовою для написання нечистого коду, ефективно обходить це)