- •Оглавление
- •От автора
- •Структура
- •Пояснения и обозначения
- •Демонстрация кунг-фу
- •Теория Основные понятия и типы данных
- •Кортежи
- •Функции, операторы
- •Полиморфные типы данных
- •Чтение сигнатур типов
- •Простейшие функции и операторы
- •Арифметические функции
- •Логические функции
- •Списочные функции
- •Кортежные функции
- •Создание своих функций
- •Способ 1. Определение функции как выражения от параметров:
- •Способ 2. Несколько определений одной функции:
- •Способ 3. Определение функции через синоним:
- •Способ 4. Лямбда функция (анонимная функция):
- •Способ 5. Частичное применение функции:
- •Образцы и сопоставление с образцом
- •Синтаксический хлеб и синтаксический сахар
- •Условия и ограничения
- •Локальные определения
- •Двумерный синтаксис
- •Арифметические последовательности
- •Замыкания списков
- •Функциональное мышление
- •Рекурсия как основное средство
- •Ручная редукция выражений
- •Думаем функционально, шаг раз
- •Думаем функционально, шаг два: аккумуляторы
- •Реализация простейших списочных и прочих функций
- •Думаем функционально, шаг три: хвостовая рекурсия
- •Еще раз о рекурсии
- •Полезные хитрости языка
- •Ленивые вычисления и строгие функции
- •Бесконечные списки
- •Функция show
- •Совсем немного о классах
- •Функция read
- •Функция error
- •Побочные эффекты и функция trace
- •Функции высших порядков
- •Мотивация
- •Функция map
- •Функция filter
- •Композиция функций
- •Функция foldr
- •Функция foldl
- •Свертки: разбор полетов
- •Выявление общей функциональности
- •Стандартные функции высших порядков
- •Еще немного про строгие функции
- •Создание своих типов данных
- •Простые перечислимые типы данных
- •Контейнеры
- •О сравнении, отображении и прочих стандартных операциях
- •Параметрические типы данных
- •Сложные типы данных
- •Тип данных Maybe
- •Рекурсивные типы данных: списки
- •Рекурсивные типы данных: деревья
- •Ввод-вывод
- •Простейший ввод-вывод
- •Объяснение кухни
- •Пример программы, производящей нетривиальное преобразование текстового файла
- •Пример решения задачи: Поиск в пространстве состояний
- •Через массивы и последовательность промежуточных состояний
- •Решение для тех, кто не хочет разбираться сам
- •Через списки, лог истории и уникальную очередь
- •Решение для тех, кто не хочет разбираться сам
- •Задачник
- •Пояснения и обозначения
- •Лабораторная работа 1 Простейшие функции
- •Простейшие логические функции
- •Простейшие списочные функции
- •Лабораторная работа 2 Символьные функции
- •Простейшие кортежные функции
- •Теоретико-множественные операции
- •Сортировка
- •Арифметические последовательности
- •Генераторы списков
- •Лабораторная работа 4 Бесконечные списки
- •Ввод-вывод
- •Нетривиальные функции
- •Лабораторная работа 5 Простые числа и факторизация
- •Деревья
- •Деревья вычислений
- •Дополнительные задания для самостоятельной работы Задания с Project Euler
- •Простейший инструментарий Установка WinHugs и начало работы
- •Работа с интерпретатором WinHugs в интерактивном режиме
- •Команды интерпретатору
- •Работа с модулями
- •Список рекомендуемой литературы и электронных ресурсов
Ввод-вывод
Любая программа будет производить действия ввода-вывода (в самом широком смысле – от вывода чего-нибудь на экран до чтения и записи в порты устройств), если мы вообще хотим получить от нее какой-нибудь полезный выхлоп, кроме загрузки центрального процессора и обогревания комнаты.
Однако даже простая печать на экран в Haskell сопряжена с определенного рода идейными проблемами. Мы ведь говорили о том, что все функции в Haskell не могут иметь побочные эффекты, а что такое ввод-вывод, как не побочные эффекты?
Итак, с одной стороны, функции ничего не могут печатать на экран и читать с клавиатуры, – а с другой стороны, они должны это делать, иначе как программой пользоваться? Для разрешения этой проблемы был использован довольно серьезный механизм – монады. Это такая абстрактная концепция, которая очень много чего может моделировать, и вот выяснилось, что ввод-вывод тоже отлично в монады укладывается.
Нет, чудес не будет, конечно – функциям придется разрешить производить побочные эффекты. Однако система ввода-вывода в Haskell позволяет явно разделить те части программы, где происходит ввод-вывод, где всякие разные плохие побочные эффекты – а где все тот же чистый строгий декларативный код без побочных эффектов.
С первого взгляда (а честно говоря, и со второго, и с третьего тоже), монады – это ужас, летящий на крыльях ночи. Монадами пугают взрослые программисты своих маленьких программистиков. Однако хорошая новость для вас заключается в том, что, как гласит популярная аналогия, понимание монад требуется для работы с вводом-выводом не больше, чем понимание теории групп для использования простой арифметики. И это действительно так. Мы начнем с того, что напишем простейшую программу, производящую ввод-вывод, и вы убедитесь, что это очень просто. А потом попробуем заглянуть немножко за занавес, чтобы увидеть всю (ну, как минимум, часть) кухни, которая там творится.
В монадах (как и в любой другой абстрактной концепции, впрочем) разобраться можно двумя путями. Можно просто принять абстрактные определения, пропустить их через себя, не пытаясь вдумываться в их жизненный смысл – и потом уже пытаться смотреть частные случаи, проявления этих абстракций. А можно идти с другой стороны – сначала разобраться в простом частном применении, потом в другом применении, потом в третьем – а потом увидеть во всех них общую абстрактную часть, и понять ее смысл через примеры ее частных проявлений.
Я надеюсь, что мои простые объяснения монады ввода-вывода потом помогут вам понять, что такое монады в целом, – я за этот путь.
Простейший ввод-вывод
Итак, вот простейшая программа, которая читает входной текстовый файл, приводит все буквы к верхнему регистру, а потом пишет выходной файл:
main :: IO ()
main = do
contents <- readFile "input.txt"
writeFile "output.txt" (process contents)
process :: String -> String
process s = map toUpper s
Вот, собственно, и все. Видите тут две части программы? Вторая часть для вас должна быть вполне понятна – тут обычная функция process, преобразующая строку в строку. Это действительно обычная функция, никаких побочных эффектов у нее нет и быть не может.
А вот функция main – это как раз та самая часть программы, что отвечает за ввод-вывод, это специфическая функция. В ней мы связываем и именем contents все содержимое файла input.txt, потом преобразовываем это содержимое с помощью функции process, и записываем результат в файл output.txt.
Между двумя этими функциями проходит незримая граница, отделяющая чистый функциональный мир строгих функций без всяких побочных эффектов – от грязного императивного чистилища.
Ну вот, а вы боялись. Усложняйте функцию process, и делайте преобразование файла любой сложности. Проблемы у вас возникнут только тогда, когда потребуется смешивать в лапшу чистые вычисления и взаимодействие с пользователем: если потребуется что-то запросить у пользователя (или прочитать из файла), потом что-то посчитать, потом опять что-то запросить, и так далее. В этом случае – ничего не поделаешь, придется разбираться с кухней.