[Перевод] Как изучить Haskell всего за 15 лет
Средний 9 мин Блог компании RUVDS.com Haskell *Функциональное программирование *Учебный процесс в IT Кейс Перевод Автор оригинала: Evan Silberman
Haskell — это язык программирования, изобретённый в 20-м веке шотландскими логиками в качестве пранка (вероятно). Примерно пятнадцать лет назад я начал изучать Haskell по причинам, которые уже и не упомню. Сегодня я наконец написал полезную программу на Haskell и уверен, что смогу сделать это снова, если мне когда-нибудь понадобится ещё одна компьютерная программа.
Я не знаю, как изучал функциональное программирование в целом и Haskell в частности. В 2006-м я следил за проектами why the lucky stiff и читал передовой тамблелог Леа Нойкирхен Anarchaia, и какой-то из этих источников познакомил меня с миром за пределами ООП. В декабре 2006 года Леа опубликовала на Anarchaia ссылку на Pandoc, и тогда я впервые узнал о своём любимом ПО и языке, на котором оно было написано.
К первому году учёбы в колледже (2007–2008) я уже точно слышал о функциональном программировании и Haskell, потому что решил, что нужно научиться пользоваться им. Я приступил к независимому исследованию функционального программирования с помощью своего соседа по общежитию, который был фанатом Common Lisp и пользователем Emacs с четырьмя ЖК-мониторами. Я читал учебник Programming in Haskell Грэма Хаттона, который, наверно, рекомендовали на веб-странице Haskell. Не помню, получил ли какие-то оценки за независимое исследование, но, кажется, успешно установил GHC. Бог его знает, как мне это удалось. Можно было ведь использовать Fink или MacPorts! Уверен, что я слышал об M-word. Но на самом деле Haskell я так и не изучил.
Позже в том же году вышла Real World Haskell Брайана О’Салливана, Дона Стюарта и Джона Герцена. В названии книги была фраза «реальный мир»! Рано или поздно я купил книгу и даже что-то из неё прочитал. Компьютер у меня оставался тот же, поэтому если бы я действительно пытался писать код, то, вероятно, мне не нужно было заново устанавливать GHC. Я плохо справлялся (и справляюсь) с повторением любых технических туториалов и выполнением упражнений с начала до конца. И оказалось, что пролистывание книги без реальной работы оказывается не особо полезным. Так что я снова не обучился Haskell.
В 2009 году у меня появился нетбук. Помните нетбуки? Тогда некоторых из вас ещё даже не было на свете! Я купил Asus Eee с какими-то ещё цифрами в названии и установил на него Arch Linux. Вероятно, из-за своего безнадёжного восхищения Haskell я выбрал в качестве фреймового оконного менеджера Xmonad. Конфигурация Xmonad — это «просто» программа на Haskell, и вы, по сути, перекомпилируете с её помощью Xmonad для перенастройки оконного менеджера. Разве не здорово? Итак, у меня наконец появилась среда для написания кода на Haskell с какой-то конкретной целью. Не думаю, что узнал много о программировании на Haskell благодаря настройке Xmonad. Пример конфигурации был очень подробным, поэтому, наверно, при настройке я больше убирал, чем добавлял, урезав свою конфигурацию до схемы с одним полным экраном и одним раздельным. Настроил привязки клавиш, установил что-то в панель состояния и тому подобное. В какой-то момент я перестал экспериментировать с ним и продолжил работать с ноутбуком. Успех!
После колледжа я не пытался использовать Haskell, за исключением пары случайных попыток, которые ни к чему не привели. Однако я часто пользовался ПО, написанным на Haskell. Возможно, даже именно потому, что оно было написано на Haskell. Это точно стало причиной того, что я попробовал hledger вместо ledger, когда я впервые начал пробовать вести свою бухгалтерию в простых текстовых файлах примерно в 2011-2012 годах. Оказалось, что когда ты безработный, следить за своими финансами очень печально. Возможно, это повлияло и на моё решение при учёбе в колледже попробовать Darcs в качестве первой распределённой системы контроля версий вместо только зарождавшегося тогда Git. В последние годы я не пользовался Darcs достаточно часто, чтобы сказать, что я по-прежнему считаю его лучшим, но это вполне может быть правдой! Поэтому я не забывал о Haskell, пока работал в местах, требовавших использования Ruby, Perl, Python, JavaScript и большого количества SQL.
Наконец, я совершил большой шаг в изучении Haskell, научившись Elm. Elm напоминает Haskell (и это сделано намеренно), а его компилятор создан на Haskell, но в том, где материалы Haskell могут утомить обычного читателя обобщённой абстрактной чушью, использование ограничений в Elm помогло сделать его более дружелюбным. В 2016 году выпустили Elm 0.17, и разработчики сосредоточили своё внимание на The Elm Architecture и интерактивных браузерных приложениях. Это стало серьёзным событием, повысившим популярность Elm, и оно проникло в моё сознание в подходящий момент.
В то время на моей работе всё самое крутое и новое в области JavaScript создавалось при помощи AngularJS. Я едва ли вспомню, как он должен был использоваться, и не могу избавиться от ощущения, что мы делали это неправильно. Он походил на безыдейное смешение состояний, шаблонов и мутаций. Двусторонняя привязка данных запутывала меня, а любое разделение логики и представления можно было легко обойти, что ещё больше усложняло работу. Я выбрал Elm для создания автономной страницы дашборда, генерирующей удобный фильтруемый обзор данных, предварительно собранных из различных источников. Создание приложения на Elm в соответствии с Elm Architecture увлекло мой крошечный мозг: у тебя есть состояние, веб-страница рендерится как чистая функция состояния, когда взаимодействуешь с ней, она отправляет сообщение в приложение, ты обрабатываешь это сообщение и создаёшь новое состояние. Красивый кружок из стрелок, двигающийся в одном направлении. Я разработал целое интерактивное клиентское приложение для браузера, хотя и только для чтения; всё это я сделал в одиночку, чего раньше не случалось, и это оказалось очень приятно.
Elm ограничивает программиста, позволяя создавать один тип приложения с ограниченным множеством абстракций на одной платформе. [Создатель и великодушный пожизненный диктатор Elm на самом деле не рассматривал веб-клиент как окончательную и единственную платформу Elm. Тем не менее, сейчас он прячется в каком-то подземном бункере, а последний релиз Elm вышел более четырёх лет назад, так что пока Эван (мой тёзка!) не выйдет на связь и не представит что-то новое, Elm остаётся именно таким.] Работа в рамках этих ограничений с наличием реального проекта — отличный способ не только выучить язык, но и понять более общие, независимые от языка принципы, на которых создаётся язык и платформа.
Наглядный пример: кодирование и декодирование данных JSON в типы предметных областей. В популярных динамических языках (Perl/Python/Ruby/JS) распространено программирование на основе словарей: создание бизнес-логики на базе словарей с косвенно ожидаемым множеством ключей — быстрый и несложный процесс. Вызвав внешний API и декодировав его как JSON, вполне естественно просто взять полученный из этого словарь и передавать его, не проверяя в точке декодирования. Когда приложение вылетает, потому что в определённых ситуациях отсутствует ключ из этого словаря или он содержит неожиданный null, вы, скорее всего, добавите в точке использования защиту от null и продолжите заниматься своими делами.
Поддержка JSON в Elm вынуждает вас явным образом декодировать ответы JSON в моделируемые предметной областью алгебраические типы данных, используемые в каком-то другом месте приложения. Нет поддержки возможности простого декодирования запроса в однородный словарь произвольной глубины: необходимо сопоставить ответ с чем-то и нужно обрабатывать случаи, когда декодирование заканчивается сбоем из-за несоответствия данных. В результате вы получаете в своей программе чёткую границу между внешним миром и данными, с которыми работаете на самом деле.
Так как мне понравился Elm и я знал, что изучаю вещи, которые вполне можно использовать в Haskell, я снова начал искать способы его освоения. Мне подвернулась Shake — библиотека Haskell для создания систем сборки. Во время докладов на конференции, которую мне пришлось посетить, но необязательно было слушать, я начал создавать сборщик статических сайтов для папки с Markdown-документами, которые мы с коллегой начали собирать в качестве внутренней документации. Как и в случае с конфигурацией Xmonad несколько лет назад, мне не нужно было особо знать Haskell, чтобы следовать подсказкам Shake на пути к работающему решению. Это развило мою способность к созданию качественного сайта документации, но в то же время ещё и означало, что даже после того, как я повысил своё понимание при помощи Elm, у меня оставались сомнения, что я понял Haskell по-настоящему.
Примерно в этот период своей жизни я смотрел замечательный доклад Гэри Бернхарда Boundaries, в котором рассказывается об использовании неизменяемых данных и чистых функций в контексте объектно-ориентированных динамических языков. Разумеется, мне понравилась эта идея. На следующей моей работе основным языком был Python, у которого недавно появились dataclass, и я структурировал на их основе несколько разных блоков кода, даже в старых кодовых базах, которые сильно различались по структуре. Я начал говорить, что мне нравится писать на Python, как будто это Haskell, хотя по-прежнему не имел особого опыта написания кода на Haskell.
Моим любимым примером такого подхода стал код в контексте приложения, управляющего графиками возвратов средств для внутреннего портфеля займов. Прежний разработчик, уже ушедший из компании, реализовал процесс генерации графиков на основе словарей, распределив бизнес-логику графиков выплат займов по разным функциям, соединив их в путаницу с обработчиками запросов веб-приложения и запросами обновления баз данных. Когда клиент указал на несогласованности и ошибки в обновлении графиков после платежей или других изменений, было сложно найти, где происходила логическая ошибка, и устранить её надёжным образом.
Когда мне всё это надоело, я переписал генерацию графиков платежей с нуля. Основная задача заключалась в следующем: имея последовательность транзакций, которые уже происходили с займом, вывести график амортизированных платежей до полной выплаты. Нужно было учесть различные особенности программы займов, например, заимствование дополнительной суммы после начала выплат. Я независимо от схемы базы данных определил модель для последовательности событий, которые могут происходить с займом, и превратил каждую из этих операций в dataclass. Затем написал функцию (императивную в реализации, но не хранящую состояний) для генерации оставшихся выплат основной суммы и процентов займа с заданными условиями и уже совершёнными платежами. Чтобы учесть пограничные случаи, я написал тесты Hypothesis, которые, по сути, выполняли функцию генерации графика и проверяли, что окончательный баланс после применения каждого платежа по графику равен нулю. (Это сработало; я устранил несколько багов.) Наконец, в совершенно другом файле я написал код, который транслировал эту структуру данных обратно в записи базы данных, сохраняющей график для применения в веб-приложении. [Можно вполне обоснованно сказать, что если график выплаты займов можно сгенерировать без хранения состояний из условий займов и множества прошлых транзакций, поэтому не нужно хранить строки базы данных для выплат по графику. Увы, сказавший это не занимался проектированием приложения в самом начале.] Всё это замечательно заработало и я наконец смог понять, что же на самом деле выполняет самая важная часть бизнес-логики. Эту работу я проделал уже больше года назад, но по-прежнему с удовольствием вспоминаю её.
Что касается программирования на Haskell, моя следующая веха настала, когда я прочитал превосходную статью Джека Келли Text-Mode Games as First Haskell Projects. Она разожгла во мне огонь, и я решил попробовать. Ничего особо выдающегося я не создал, но написал небольшое игровое существо, способное порождать монстров, с которыми сражается игрок или убегает от них. У монстров были рандомизированные очки здоровья и урон от атаки. Я всё равно не чувствовал, что готов писать полезный Haskell, но эта небольшая попытка, по крайней мере, была забавной и живительной.
Реальный прорыв случился, когда меня уволили в октябре 2023 года и я перешёл на фриланс.
К моему удивлению, благодаря тому, что я десять с лишним лет писал посты на нишевом форуме, первый клиент появился очень быстро. Я договорился о почасовой оплате, поэтому нужно было начать отслеживать время. Я набросал крошечную схему для отслеживания времени в SQLite и быстренько написал шелл-скрипт для фиксации начала и конца работы. Я обрабатывал всё необходимое для отчётности и отправки счетов, комбинируя соответствующие запросы в repl SQLite и нажимая на стрелку вверх. Я знал, что для обработки всего этого хочу написать реальную программу, поэтому начал писать её в январе. На Haskell.
И я преуспел. Написал настоящую, приносящую пользу программу на Haskell. Она парсит опции командной строки и общается с базой данных как реальная программа. Я могу фиксировать время работы и заносить задним числом часы, которые забыл указать. Она преобразует время UTC в мой часовой пояс. Она может выводить часы работы, сгруппированные по дню или месяцу. Она передаёт функции в функции. У неё есть типы данных для аккаунтов моего клиента, для записей времени и для различных операций, которые можно выполнять. Она не делает ничего сложного с задействованием M-слова. Весь код находится в одном файле. Он не очень красив, и не думаю, что я развил в себе хорошую дисциплину в работе с границами. Однако он компилируется, и когда я хочу что-то поменять, компилятор помогает мне в этом. Мой белый кит, которого я преследовал с 19 лет, полезная программа на Haskell, наконец-то загарпунена.
Чем меня восхитил Haskell, когда я был ещё подростком? Кто знает… Я писал код с нарастающим энтузиазмом с 10-11 лет, но не был вундеркиндом. Тогда у меня не было ни навыков, ни, что важнее, вкуса, которые выработались всего спустя несколько лет после работы в этой сфере. Сегодня в Haskell мне нравится то, что можно очень естественно писать на нём, создавая декларативную модель данных предметной области, надстраивать над этими данными чистые функции и взаимодействовать с реальным миром на границах программы. Это мой любимый стиль работы, как на Haskell, так и на других языках.
Скидки, итоги розыгрышей и новости о спутнике RUVDS — в нашем Telegram-канале 🚀
Теги:
- ruvds_переводы
- xmonad
- функциональное программирование
- чистые функции
- elm
Хабы:
- Блог компании RUVDS.com
- Haskell
- Функциональное программирование
- Учебный процесс в IT