мы не знаем как взаимодействовать с окружающим миром.
Самое время узнать! Сначала мы посмотрим какие проблемы связаны с реализацией взаимодействия с
пользователем. Как эти проблемы решаются в Haskell. Потом мы научимся решать несколько типичных задач,
связанных с вводом/выводом.
8.1 Чистота и побочные эффекты
Когда мы определяем новые функции или константы мы лишь даём новые имена комбинациям значений.
В этом смысле у нас ничего не изменяется. По-другому это называется функциональной чистотой (referential
transparency). Это свойство говорит о том, что мы свободно можем заменить в тексте программы любой
синоним на его определение и это никак не скажется на результате.
Функция является чистой, если её выход зависит только от её входов. В любой момент выполнения про-
граммы для одних и тех же входов будет один и тот же выход. Это свойство очень ценно. Оно облегчает
понимание поведения функции. Оно говорит о том, что функция может зависеть от других функций толь-
ко явно . Если мы видим, что другая функция используется в данной функции, то она используется в этой
функции. У нас нет таинственных глобальных переменных, в которые мы можем записывать данные из од-
ной функции и читать их с помощью другой. Мы вообще не можем ничего записывать и ничего читать. Мы
не можем изменять состояния, мы можем лишь давать новые имена или строить новые выражения из уже
существующих.
Но в этот статичный мир описаний не вписывается взаимодействие с пользователем. Предположим, что
мы хотим написать такую программу: мы набираем на клавиатуре имя файла, нажимаем Enterи программа
показывает на экране содержимое этого файла, затем мы набираем текст, нажимаем Enterи текст дописыва-
ется в конец файла, файл сохраняется. Это описание предполагает упорядоченность действий. Мы не можем
сначала сохранить текст, затем прочитать обновления. Тогда текст останется прежним.
Ещё один пример. Предположим у нас есть функция getChar, которая читает букву с клавиатуры. И
функция print, которая выводит строку на экран И посмотрим на такое выражение:
letc =getChar
in
print $c :c : []
О чём говорит это выражение? Возможно, прочитай с клавиатуры букву и выведи её на экран дважды.
Но возможен и другой вариант, если в нашем языке все определения это синонимы мы можем записать это
выражение так:
print $getChar :getChar : []
Это выражение уже говорит о том, что читать с клавиатуры необходимо дважды! А ведь мы сделали обыч-
ное преобразование, заменили вхождения синонима на его определение, но смысл изменился. Взаимодей-
ствие с пользователем нарушает чистоту функций, нечистые функции называются функциями с побочными
эффектами.
Как быть? Можно ли внести в мир описаний порядок выполнения, сохранив преимущества функциональ-
ной чистоты? Долгое время этот вопрос был очень трудным для чистых функциональных языков. Как можно
пользоваться языком, который не позволяет сделать такие базовые вещи как ввод/вывод?
126 | Глава 8: IO
8.2 Монада IO
Где-то мы уже встречались с такой проблемой. Когда мы говорили о типе STи обновлении значений. Там
тоже были проблемы порядка вычислений, нам удалось преодолеть их с помощью скрытой передачи фиктив-
ного состояния. Тогда наши обновления были чистыми , мы могли безболезненно скрыть их от пользователя.
Теперь всё гораздо труднее. Нам всё-таки хочется взаимодействовать с внешним миром. Для обозначения
внешнего мира мы определим специальный тип и назовём его RealWorld:
module IO(
IO
) where
data RealWorld = RealWorld
newtype IOa = IO( ST RealWorlda)
instance Functor
IO where ...
instance Applicative
IO where ...
instance Monad
IO where ...
Тип IO(от англ. input-output или ввод-вывод) обозначает взаимодействие с внешним миром. Внешний
мир словно является состоянием наших вычислений. Экземпляры классов композиции специальных функций
такие же как и для ST(а следовательно и для State). Но при этом, поскольку мы конкретизировали первый
параметр типа ST, мы уже не сможем воспользоваться функцией runST.
Тип RealWorldопределён в модуле Control.Monad.ST, там же можно найти и функцию:
stToIO :: ST RealWorlda -> IOa
Читать дальше