Введение

Что такое ФП?

Функциональное программирование — парадигма программирования, в которой функции — первоклассные. В частности, функции можно передавать на вход другим функциям и получать их на выходе.

«Иногда, элегантная реализация — это функция. Не метод. Не класс. Не фреймворк. Просто функция»

Джон Кармак (id Software; Doom, Quake, etc.)

Для того, чтобы собирать (композировать) из функций более сложные и не сойти с ума, функции должны быть чистыми, т.е. у них не должно быть побочных эффектов. Функция, получающая один и тот же набор значений на входе, должна выдавать один и тот же результат. Из этой же идеи появляется и концепция неизменяемости. Если вы выполняете какие-то действия с данными (например, со списком), вы не меняете старый список в памяти, а создаёте новый (но при этом он может делить часть данных со старым: например, новая голова upd1 и старый хвост orig2).

immutability-list.png

Также может быть полезно иметь достаточно выразительную систему типов, чтобы не хранить в голове, что же именно передаётся в функцию и что возвращается. Это помогает переложить многие проверки на компилятор и избавиться от какого-то класса тестов. Строгая типизация помогает писать программы, корректные по построению.

Функциональное программирование будет изучаться нами на примере языка 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. Она отслеживает указанный файл, компилирует его заново при каждом изменении и выводит ошибки компиляции.