– не чистая функция, потому что её результат может быть неодинаковым при последовательных вызовах. Вот почему она как бы «запачкана» конструктором типов
>IO
, и мы можем получить данные только внутри действий ввода-вывода, имеющих в сигнатуре типа маркёр
>IO
. Так как код для ввода-вывода также «испачкан», любое вычисление, зависящее от «испачканных»
>IO
-данных, также будет давать «грязный»результат.
Если я говорю «испачканы», это не значит, что мы не сможем использовать результат, содержащийся в типе >IO
в чистом коде. Мы временно «очищаем» данные внутри действия, когда связываем их с именем. В выражении >name <– getLine
образец >name
содержит обычную строку, представляющую содержимое ящика.
Мы можем написать сложную функцию, которая, скажем, принимает ваше имя как параметр (обычная строка) и предсказывает вашу удачливость или будущее всей вашей жизни, основываясь на имени:
>main = do
> putStrLn "Привет, как тебя зовут?"
> name <– getLine
> putStrLn $ "Вот твоё будущее: " ++ tellFortune name
Функция >tellFortune
(или любая другая, которой мы передаём значение >name
) не должна знать ничего про >IO
– это обычная функция >String
>–>
>String
.
Посмотрите на этот образец кода. Корректен ли он?
>nameTag = "Привет, меня зовут " ++ getLine
Если вы ответили «нет», возьмите с полки пирожок. Если ответили «да», убейте себя об стену… Шучу, не надо! Это выражение не сработает, потому что оператор >++
требует, чтобы оба параметра были списками одинакового типа. Левый параметр имеет тип >String
(или >[Char]
, если вам угодно), в то время как функция >getLine
возвращает значение типа >IO String
. Вы не сможете конкатенировать строку и результат действия ввода-вывода. Для начала нам нужно извлечь результат из действия ввода-вывода, чтобы получить значение типа >String
, и единственный способ сделать это – выполнить что-то вроде >name <– getLine
внутри другого действия ввода-вывода. Если мы хотим работать с «нечистыми» данными, то должны делать это в «нечистом» окружении!… Итак, грязь от нечистоты распространяется как моровое поветрие, и в наших интересах делать часть для осуществления ввода-вывода настолько малой, насколько это возможно.
Каждое выполненное действие ввода-вывода заключает в себе результат. Вот почему наш предыдущий пример можно переписать так:
>main = do
> foo <- putStrLn "Привет, как тебя зовут?"
> name <– getLine
> putStrLn ("Привет, " ++ name ++ ", ну ты и хипстота!")
Тем не менее образец >foo
всегда будет получать значение >()
, так что большого смысла в этом нет. Заметьте: мы не связываем последний вызов функции