IO: do для мира и чистое ядро
Финал блока абстракций и мост к первому проекту. Весь раздел мы держали программу чистой, а ввод-вывод называли грязным миром и обходили стороной. Пора посмотреть на него прямо. Главная мысль урока:
IO aэто не действие, которое уже случилось, а рецепт действия, обычное значение, которое выполняет только рантайм. В этом уроке:getLine,putStrLn, чтение и запись файлов, аргументы командной строки,interact, и архитектурный приём всего раздела, отделение чистого ядра от грязной оболочки.
Идея
В любом другом языке print("привет") это инструкция: дошёл до строки, напечатал. В Haskell так нельзя, и вот почему. Чистая функция обязана давать один и тот же ответ на одни и те же аргументы и ничего не менять в мире. А getLine каждый раз возвращает разное, putStrLn меняет экран. Это прямое нарушение чистоты. Как Haskell мирится с этим, не отказываясь от чистоты?
Хитрость в том, что IO a это не выполненное действие, а его описание. Значение типа IO String это рецепт: вот как добыть строку из мира, когда придёт время. Сам рецепт чистый, его можно передавать, складывать, хранить в списке. Готовку запускает ровно одно место, рантайм, когда исполняет main:
main :: IO ()
main = putStrLn "привет, мир"
main имеет тип IO (): рецепт, который при исполнении даёт пустой результат () (юнит, ничего полезного) и попутно меняет мир. putStrLn "привет" это не печать, это значение, описывающее печать. Программа целиком это один большой рецепт main, который рантайм разворачивает и исполняет. IO это рецепт, а не выполнение.
IO это монада, и do работает как раньше
Хорошая новость: ты уже всё умеешь. IO это монада из прошлого урока, и do-нотация над ней это та же самая do-нотация:
main :: IO ()
main = do
putStrLn "Как тебя зовут?"
name <- getLine
putStrLn ("Привет, " <> name <> "!")
Разбери по правилам разворачивания из урока 12. name <- getLine это getLine >>= \name -> ...: исполни рецепт getLine, получи строку, назови её name. Строки без <- (как putStrLn ...) это шаги через >>, чей результат () никому не нужен. Никакой новой механики: IO это просто ещё одна монада, у которой эффект это взаимодействие с миром.
Базовый словарь ввода-вывода:
putStrLn :: String -> IO () -- напечатать строку и перевод строки
putStr :: String -> IO () -- без перевода строки
print :: Show a => a -> IO () -- putStrLn . show, для любого Show
getLine :: IO String -- прочитать строку со ввода
print это putStrLn . show: печатает что угодно с инстансом Show из урока про типоклассы. Вот зачем deriving Show нужен в каждом учебном типе.
премиум-урок
Продолжение в премиуме: 1 200 ₽ в месяц или 10 000 ₽ в год. Вместе с ним серверные проверки задач и школьные токены AI-коуча.
Оплата пока не подключена: премиум и курсы открываются промокодом.