Введение
Что такое ФП?
Функциональное программирование — парадигма программирования, в которой функции — первоклассные. В частности, функции можно передавать на вход другим функциям и получать их на выходе.
«Иногда, элегантная реализация — это функция. Не метод. Не класс. Не фреймворк. Просто функция»
— Джон Кармак (id Software; Doom, Quake, etc.)
Для того, чтобы собирать (композировать) из функций более сложные и не сойти с ума, функции должны быть чистыми, т.е. у них не должно быть побочных эффектов. Функция, получающая один и тот же набор значений на входе, должна выдавать один и тот же результат. Из этой же идеи появляется и концепция неизменяемости. Если вы выполняете какие-то действия с данными (например, со списком), вы не меняете старый список в памяти, а создаёте новый (но при этом он может делить часть данных со старым: например, новая голова upd1 и старый хвост orig2).
Также может быть полезно иметь достаточно выразительную систему типов, чтобы не хранить в голове, что же именно передаётся в функцию и что возвращается. Это помогает переложить многие проверки на компилятор и избавиться от какого-то класса тестов. Строгая типизация помогает писать программы, корректные по построению.
Функциональное программирование будет изучаться нами на примере языка Haskell, который является функциональным языком программирования со статической типизацией и ленивыми вычислениями.
Установка
В курсе Stepik, который вы начнёте проходить, немного устаревшая версия компилятора. Более новый компилятор можно установить следующими способами:
- используя пакетный менеджер вашей системы (apt, rpm, emerge, …); пакет, скорее всего, будет называться
ghcили сходным образом; - установив ghcup; после этого вы сможете запустить
ghcup tuiи выбрать нужные компоненты для установки; вам потребуетсяghcиcabal.
Также в курсе мы будем использовать систему сборки пакетов cabal, которую можно установить тем же способом (либо cabal будет установлен автоматически). Для простых задач, которых в курсе будет большинство, использовать cabal не обязательно. Перед решением тех задачах, где он потребуется, мы его обсудим.
Работа с компилятором и интерпретатором
Для простых задач достаточно использовать компилятор и интерпретатор. В отличие от некоторых языков, Haskell можно как скомпилировать в бинарный файл, так и интерпретировать. Это позволяет ускорить цикл разработки: становится возможно запускать и тестировать отдельные функции. Интерпретатор считывает выражения, которые подаются ему на вход (в частности, файл с вашей программой), вычисляет их и печатает результат. Этот цикл называется REPL — Read, Eval, Print Loop. Такое же название перешло и на саму систему интерпретации.
Для запуска компиляции нужно выполнить команду ghc --make <ваш-файл>, например, ghc --make Solution.hs. Флаг --make нужен, чтобы компилятор сам нашёл импортируемые модули (из соседних файлов). Скомпилированный файл будет называться по имени вашего файла, например, Solution. Дальше вы можете его запустить, и будет выполнена функция main из вашего файла. Впрочем, для простых задач может быть проще запускать отдельные функции, без ввода-вывода. Для этого нужен интерпретатор.
Запуск интерпретатора выполняется командой ghci <ваш-файл>, например, ghci Solution.hs. После запуска откроется окно, куда вы можете вводить примерно те же выражения, что и в коде, и команды для управления интерпретатором. Вот некоторые команды:
:r— перезагружает модули. Например, вы поменяли что-либо в файле, сохранили и хотите проверить заново. Локальные определения (которые вы вводили в REPL) при этом не сохраняются. Сокращение от:reload.:q— выход из REPL. Сокращение от:quit.:l <файл>, например,:l Solution.hs— загружает файл в REPL. Сокращение от:load.:m + <модуль>, например,:m + Data.Listили:m + Helpers— импортирует модуль в REPL. Сокращение от:module +.:m - <модуль>— выгружает модуль из REPL. Сокращение от:module -.:browse <модуль>— показывает список определений из модуля.:t <выражение>, например,:t "Hello, " ++ "world!"— показывает тип выражения. Сокращение от:type.:doc <определение>, например,:doc ++или:doc Char— показывает справку об определении (если она есть).:h,:help— показывает справку о командах REPL.
Если вам слишком лениво каждый раз перезапускать интерпретатор и проверять ошибки компиляции, вы можете установить программу ghcid. Она отслеживает указанный файл, компилирует его заново при каждом изменении и выводит ошибки компиляции.