Alejandro Serrano Mena Beginning Haskell: A Project-Based Approach Алехандро Серано Мена ИЗУЧАЕМ HASKELL А. Мена Изучаем Haskell. Библиотека программиста Серия «Библиотека программиста» Перевел с английского Н. Вильчинский Заведующий редакцией П. Щеголев Ведущий редактор Ю. Сергиенко Литературный редактор А. Жданов Художник В. Шимкевич Корректор С. Беляева, Н. Викторова Верстка Л. Соловьева ББК 32.973.2-018.1 УДК 004.43 Мена А. М45 Изучаем Haskell. Библиотека программиста. — СПб.: Питер, 2015. — 464 с.: ил. — (Серия «Библиотека программиста»). ISBN 978-5-496-01188-4 Эта книга поможет вам быстро освоить базовые концепции языка программирования Haskell, его библиотеки и компоненты, а также заложит основы функциональной парадигмы программирования, ко- торая становится все более значимой в современном мире разработки ПО. Книга предлагает проектный подход к освоению материала, используя в качестве прототипа проект реализации интернет-магазина. Здесь рассматривается экосистема языка Haskell и его вспомогательных средств, инструменты Cabal для управление проектами, модули HUnit и QuickCheck для тестирования программ, фреймворк Scotty для разработки веб-приложений, Persistent и Esqueleto — для управления базами данных и многие другие компоненты и библиотеки Haskell. 12+ (В соответствии с Федеральным законом от 29 декабря 2010 г. № 436-ФЗ.) ISBN 978-1430262503 англ. © Apress ISBN 978-5-496-01188-4 © Перевод на русский язык ООО Издательство «Питер», 2015 © Издание на русском языке, оформление ООО Издательство «Питер», 2015 Права на издание получены по соглашению с Apress. Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав. Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как на- дежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за возможные ошибки, связанные с использованием книги. ООО «Питер Пресс», 192102, Санкт-Петербург, ул. Андреевская (д. Волкова), д. 3, литер А, пом. 7Н. Налоговая льгота — общероссийский классификатор продукции ОК 034-2014, 58.11.12.000 — Книги печатные профессиональные, технические и научные. Подписано в печать 28.10.14. Формат 70×100/16. Усл. п. л. 37,410. Тираж 1000. Заказ 0000. Отпечатано в полном соответствии с качеством предоставленных издательством материалов в ГППО «Псковская областная типография». 180004, Псков, ул. Ротная, 34. Оглавление Об авторе . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 О научном редакторе . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 Благодарности . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Зачем изучать функциональное программирование . . . . . . . . . . . . . 11 Почему вам нужна эта книга . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Часть I . Первые шаги . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Глава 1 . Настоящий функциональный язык . . . . . . . . . . . . . . . . . 14 Почему именно Haskell? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 История Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Ваша рабочая среда . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Первые шаги в использовании GHCi . . . . . . . . . . . . . . . . . . . . . . . . . 24 Магазин, продающий машины времени . . . . . . . . . . . . . . . . . . . . . . 26 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Глава 2 . Объявление модели данных . . . . . . . . . . . . . . . . . . . . . . . 28 Работа с символами, числами и списками . . . . . . . . . . . . . . . . . . . . . 28 Создание нового проекта . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 Определение простых функций . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 Работа с типами данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Сопоставление с образцом . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Записи . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Глава 3 . Многократное использование кода с помощью списков . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 Параметрический полиморфизм . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 Функции в качестве аргументов . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 Подробнее о модулях . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 Углубленное изучение списков . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 Формирователи списков . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 6 Оглавление Haskell-оригами . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 Глава 4 . Контейнеры и классы типов . . . . . . . . . . . . . . . . . . . . . . 100 Использование пакетов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 Контейнеры: проекции, множества, деревья и графы . . . . . . . . . . . 108 Специальный полиморфизм: классы типов . . . . . . . . . . . . . . . . . . . 120 Двоичные деревья для минимальной цены . . . . . . . . . . . . . . . . . . . 129 Классы типов, связанные с контейнерами . . . . . . . . . . . . . . . . . . . 134 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 Глава 5 . Лень и бесконечные структуры . . . . . . . . . . . . . . . . . . . 139 Бесконечное количество машин времени . . . . . . . . . . . . . . . . . . . . 139 Модель ленивых вычислений . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 Профилирование с помощью GHC . . . . . . . . . . . . . . . . . . . . . . . . . 152 Строгие аннотации . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 Часть II . Анализ данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 Глава 6 . Ознакомление с клиентами с помощью монад . . . . . . . 162 Анализ данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 Исследование монад . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174 Различные виды состояний . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 Глава 7 . Другие рекомендуемые монады . . . . . . . . . . . . . . . . . . . 194 Возвращение более одного значения . . . . . . . . . . . . . . . . . . . . . . . 194 Неудачи и альтернативы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 Вывод ассоциативных правил . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200 Задачи поиска . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207 Монада Logic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208 Возрождение монад и списков . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223 Глава 8 . Использование нескольких ядер . . . . . . . . . . . . . . . . . . 224 Параллелизм, одновременность, распределенность . . . . . . . . . . . . 224 Монада Par . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226 Программная транзакционная память . . . . . . . . . . . . . . . . . . . . . . . 232 Облачные решения для Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248 Оглавление 7 Часть III . Использование ресурсов . . . . . . . . . . . . . . . . . . . . . . . . . . . 249 Глава 9 . Работа с файлами: ввод-вывод и библиотека conduit 250 Базовые ввод и вывод . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 Случайность . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254 Работа с файлами . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257 Обработка ошибок . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261 Потоковый ввод-вывод данных с помощью библиотеки conduit . . . . 270 За пределами текстовых файлов . . . . . . . . . . . . . . . . . . . . . . . . . . 277 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281 Глава 10 . Создание и синтаксический разбор текста . . . . . . . . . 282 Пять текстовых типов данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282 Создание текста со скоростью ветра . . . . . . . . . . . . . . . . . . . . . . . 287 Синтаксический разбор с использованием пакета attoparsec . . . . . . 290 Представление новых классов типов . . . . . . . . . . . . . . . . . . . . . . . 296 Не нужно излишеств: используйте формат JSON . . . . . . . . . . . . . . 304 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310 Глава 11 . Безопасный доступ к базам данных . . . . . . . . . . . . . . . 311 Средства доступа к базам данных . . . . . . . . . . . . . . . . . . . . . . . . . 311 Соединение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314 Схемы и переносы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316 Запросы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323 Вставка, обновление и удаление . . . . . . . . . . . . . . . . . . . . . . . . . . 330 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333 Глава 12 . Веб-приложения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334 Веб-экосистема Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334 Структура RESTful . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338 Разработка внутреннего интерфейса в среде Scotty . . . . . . . . . . . . 339 Разработка внешнего интерфейса с помощью компилятора Fay . . . 349 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354 Часть IV . Предметно-ориентированные языки . . . . . . . . . . . . . . . . . 355 Глава 13 . Строгие типы для описания предложений . . . . . . . . . 356 Предметно-ориентированные языки . . . . . . . . . . . . . . . . . . . . . . . . 356 Безопасность языка выражений . . . . . . . . . . . . . . . . . . . . . . . . . . . 362 Зависимая типизация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365 Программирование на уровне типов в Haskell . . . . . . . . . . . . . . . . . 370 Функциональные зависимости . . . . . . . . . . . . . . . . . . . . . . . . . . . . 373 8 Оглавление Семейства типов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379 Продвижение типа и одноэлементные типы . . . . . . . . . . . . . . . . . . 385 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394 Глава 14 . Интерпретация предложений с помощью атрибутов 395 Варианты интерпретации и атрибутивные грамматики . . . . . . . . . . 395 Наша первая атрибутивная грамматика . . . . . . . . . . . . . . . . . . . . . 398 Интеграция UUAGC-кода в пакет . . . . . . . . . . . . . . . . . . . . . . . . . . 401 Интерпретация выражений . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 404 Варианты интерпретации предложений . . . . . . . . . . . . . . . . . . . . . 409 Оригами-программирование для любого типа данных . . . . . . . . . . 415 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418 Часть V . Инжиниринг магазина . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419 Глава 15 . Документирование, тестирование и проверка . . . . . . 420 Документирование двоичных деревьев с помощью утилиты Haddock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421 Блочное тестирование с помощью утилиты HUnit . . . . . . . . . . . . . . 424 Рандомизированное тестирование с помощью библиотеки QuickCheck . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430 Формальная верификация с использованием языка Idris . . . . . . . . . 434 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 438 Глава 16 . Создание архитектуры приложения . . . . . . . . . . . . . . 439 Паттерны проектирования и функциональное программирование . . 439 Рекомендации среднего порядка . . . . . . . . . . . . . . . . . . . . . . . . . . 441 Утилиты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443 Проекты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446 Выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 458 Приложение А . Дальнейшие перспективы . . . . . . . . . . . . . . . . . . 459 Haskell-ресурсы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 460 Приложение Б . Путешествие во времени с Haskell . . . . . . . . . . . 462 Об авторе Алехандро Серано Мена — горячий сторонник функционального программирова- ния, он использует Haskell для личных и коммерческих проектов уже более пяти лет. В настоящее время пишет кандидатскую диссертацию в Утрехтском универ- ситете. Область его интересов связана с разработкой систем со строгой типизацией и взаимодействием с такими системами, например, посредством языка Haskell. Он внес вклад в ряд проектов с открытым кодом, включая Mono и Nemerle. В 2011 году принял участие в программе Google Summer of Code, совершенствуя подключаемый Haskell-модуль EclipseFP для популярной среды разработки Eclipse. В 2012 году он с двумя друзьями основал компанию Nublic, деятельность которой сконцентрирована на предоставлении облачных инструментов для до- машних сред. Основная часть программного обеспечения данного проекта была создана с использованием языка Scala, сочетающего объектно-ориентированные и функциональные аспекты. Диплом по информатике и математике Алехандро получил в родном Мадридском университете. В период обучения он активно продвигал среди студентов идеи свободного программного обеспечения и функциональной парадигмы. Его статьи можно прочитать в испанском журнале «Todo Programación». О научном редакторе Жан-Филипп Моресмо пишет программы уже пятнадцать лет. Он занимался созданием веб-приложений с 1996 года и всегда испытывал интерес к интегриро- ванным средам разработки и анализу языка. Он принимает активное участие в работе сообщества по разработке приложений с открытым кодом на языке Haskell, а также входит в группу поддержки проекта EclipseFP. Его блог находится по адресу http://jpmoresmau .blogspot .com. Ж.-Ф. Мо- ресмо с женой и двумя детьми живет на юге Франции. Благодарности Прежде всего, хотел бы отметить грандиозную работу научного редактора и ре- дакции. Их комментарии и предложения представляли исключительную ценность и позволили существенно улучшить качество книги. Еще я хотел бы отдельно от- метить великолепную работу Жана-Филиппа Моресмо над EclipseFP. Написание книги — дело не только благодарное, но и хлопотное. И в те моменты, когда настроение падало до нуля, я всегда ощущал поддержку Елены. Она была со мной и в минуты размышлений, и в минуты оттачивания примеров, и в минуты бесконечных правок текста. Cемья и друзья поддерживали меня все это время, поэтому большое спасибо вам всем. Мои родители, Кармен и Хулиан, заслуживают особого упоминания: они поддерживали меня при выполнении каждого моего проекта, и как бы странно это ни звучало, на протяжении всей моей жизни. Они купили мне первый компьютер, и именно на нем я впервые начал программировать в старой доброй среде Visual Basic. Поэтому хотя бы ради справедливости нужно сказать, что без их помощи вы бы никогда не читали эту книгу. Нельзя не восхититься всем Haskell-сообществом. Наличие обширных подписок на рассылки и групповых чатов, наполненных собеседников (заметьте, умных собеседников!), всегда готовых прийти на помощь, является хорошим стимулом для проявления любопытства и познания нового. Каждая библиотека и каждый компилятор, о которых рассказывается в книге, были с любовью разработаны этим сообществом: именно они сделали Haskell таким великим языком. И в завершение хочу поблагодарить своих преподавателей и коллег из Утрехтско- го университета. Хотя мы были знакомы всего лишь пару месяцев, их страстное увлечение функциональным программированием оказало существенное влияние на книгу.