Наиболее очевидным нововведением, замеченным людьми, плохо знакомыми с Хаскеллом, является то, что существует некое разделение между нечистым миром, связанным с общением с внешним миром, и чистым миром вычислений и алгоритмов. Частый вопрос для начинающих: «Как я могу избавиться IO
, т.е. преобразовать IO a
в a
?» Путь к этому - использовать монады (или другие абстракции) для написания кода, который выполняет эффекты ввода-вывода и цепочки. Этот код собирает данные из внешнего мира, создает его модель, выполняет некоторые вычисления, возможно, используя чистый код, и выводит результат.
Что касается вышеприведенной модели, я не вижу ничего страшного в манипулировании GUI в IO
монаде. Самая большая проблема, которая возникает из-за этого стиля, состоит в том, что модули больше не могут быть компонованы, то есть я теряю большую часть моих знаний о глобальном порядке выполнения операторов в моей программе. Чтобы восстановить его, я должен применить те же рассуждения, что и в параллельном императивном коде GUI. Между тем, для нечистого, не GUI-кода порядок выполнения очевиден из-за определения оператора IO
монады >==
(по крайней мере, пока существует только один поток). Для чистого кода это вообще не имеет значения, кроме как в угловых случаях для повышения производительности или во избежание оценок, приводящих к ⊥
.
Самое большое философское различие между консольным и графическим вводом-выводом заключается в том, что программы, реализующие первый, обычно написаны в синхронном стиле. Это возможно, потому что есть (оставляя в стороне сигналы и другие дескрипторы открытого файла) только один источник событий: поток байтов, обычно называемый stdin
. Хотя GUI по своей сути асинхронны и должны реагировать на события клавиатуры и щелчки мыши.
Популярная философия функционального асинхронного ввода-вывода называется функционально-реактивным программированием (FRP). В последнее время он получил широкое распространение в нечистых, нефункциональных языках благодаря библиотекам, таким как ReactiveX , и фреймворкам, таким как Elm. В двух словах, это похоже на просмотр элементов графического интерфейса пользователя и других вещей (таких как файлы, часы, сигналы тревоги, клавиатура, мышь) в качестве источников событий, называемых «наблюдаемыми», которые излучают потоки событий. Эти события объединяются с помощью знакомых операторов , таких , как map
, foldl
, zip
, filter
, concat
, join
и т.д., для создания новых потоков. Это полезно, потому что само состояние программы можно рассматривать как scanl . map reactToEvents $ zipN <eventStreams>
программу, где N
равно количеству наблюдаемых, когда-либо рассмотренных программой.
Работа с наблюдаемыми FRP позволяет восстановить возможность компоновки, поскольку события в потоке упорядочены во времени. Причина в том, что абстракция потока событий позволяет рассматривать все наблюдаемые как черные ящики. В конечном счете, объединение потоков событий с использованием операторов возвращает некоторое локальное упорядочение при выполнении. Это заставляет меня быть более честным в отношении того, на какие инварианты на самом деле опирается моя программа, подобно тому, как все функции в Haskell должны быть прозрачными по ссылкам: если я хочу получить данные из другой части моей программы, я должен быть явным объявление объявляет соответствующий тип для моих функций. (Монада ввода-вывода, являющаяся специфичным для домена языком для написания нечистого кода, эффективно обходит это)