VI · ФП ← Курс
Глава VI · дуга ФП и корректности

Функциональное программирование — мир как преобразование.

Здесь начинается серьёзное погружение. В императивном мире программа меняет состояние; в функциональном — преобразует данные чистыми функциями, ничего не портя на месте. Сложное собирается композицией простого, а в основании лежит λ-исчисление. Это язык, на котором пишут программы, чью корректность можно доказать.

f∘gкомпозиция
λоснование
immutableбез мутаций
TRANSFORMATIO data → f → g λx. e pure x f g h y h ∘ g ∘ f данные текут, не мутируют
№ 00Сдвиг парадигмы

Не менять состояние, а применять преобразование

Императивная программа — это последовательность команд, меняющих состояние: присвоить, увеличить, перезаписать. Функциональная — это композиция преобразований: данные проходят сквозь чистые функции, и на каждом шаге рождается новое значение, а старое остаётся нетронутым. Мир описывается не как «что изменить», а как «какое преобразование применить».

Для инженера это не стилистика, а способ совладать со сложностью. Когда ничего не мутирует исподтишка, рассуждать о программе можно локально: каждая функция понятна сама по себе. Именно поэтому функциональный подход становится опорой второй дуги курса — дуги о корректности.

Математик, который не является в известной мере поэтом, никогда не будет настоящим математиком. — К. Вейерштрасс
∅
Чистота и неизменяемость

Чистая функция: только вход, только выход

Чистая функция зависит лишь от своих аргументов и не имеет побочных эффектов: при тех же входах — тот же результат, всегда. Отсюда ссылочная прозрачность: вызов можно заменить его значением, не меняя смысла программы.

PURITASчистая vs грязная refer. transparencyno side effects x f y чистая тот же вход → тот же выход x f ? скрытое состояние побочный эффект грязная
Слева — чистая функция: предсказуема и проверяема. Справа — скрытые входы и эффекты делают результат непредсказуемым.

Неизменяемость — оборотная сторона чистоты: данные не меняют на месте, а порождают новые версии. Это убирает целый класс ошибок (гонки, неожиданные мутации) и открывает дорогу безопасному параллелизму — что станет важным в дуге об устойчивых системах. На Elixir это естественно:

# чистая: результат зависит только от входа
def discount(price, rate), do: price * (1 - rate)

# данные неизменяемы: получаем новую структуру, старая цела
updated = Map.put(order, :total, 990)
∘
Композиция

Сложное — это конвейер простого

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

Композиция $h\circ g\circ f$ ассоциативна и имеет единицу (тождество) — те же законы, что у моноида из главы об алгебре. Поэтому большие конвейеры собираются предсказуемо. Функции высшего порядка (map, filter, reduce) обобщают это: они принимают функции как аргументы и применяют их к структурам.

В Elixir конвейер виден буквально — оператор |> передаёт значение дальше по цепочке преобразований:

order
|> validate()
|> apply_discount(0.1)
|> add_tax()
|> total()
PIPEf → g → h ассоциативно+ тождество order validate discount tax total
Данные текут вниз сквозь преобразования — мир как преобразование.
λ
λ-исчисление

В основании — одна идея: функция

Под функциональным программированием лежит λ-исчисление Чёрча: формальная система, где всё — функции, а вычисление есть их применение и упрощение.

Два кирпича: абстракция $\lambda x.\,e$ (функция от $x$ с телом $e$) и применение $(\lambda x.\,e)\,a$. Вычисление — это β-редукция: подставить аргумент вместо переменной.

$(\lambda x.\,e)\,a \;\to_\beta\; e[x := a]$

Тождество — $\lambda x.\,x$. Функции здесь — значения первого класса: их можно передавать, возвращать, складывать в структуры. Этого минимума достаточно для тьюринг-полноты — на чистых функциях выразимо любое вычисление. Отсюда и сила ФП: программа — это выражение, а не протокол изменений.

CALCULUSβ-редукция λx. efirst-class (λx. x+1) 4 β 4+1 5 подставить аргумент — и упростить
β-редукция: применяем функцию, подставляя аргумент, и сводим выражение к значению.
⊕
Алгебра в программах

Те же структуры, что в главе I

Функциональный код буквально алгебраичен. Композиция образует моноид; контейнеры, по которым умеют отображать, — функторы. Законы этих структур и делают сборку предсказуемой.

Моноид — ассоциативная операция с единицей. Списки под конкатенацией (единица — пустой список), числа под сложением (единица — ноль) — всё это моноиды, и потому их можно безопасно «сворачивать»:

$a \cdot (b \cdot c) = (a \cdot b) \cdot c,\qquad e \cdot a = a \cdot e = a$

⊕Моноид

Ассоциативность + единица. Даёт право сворачивать (reduce) в любом порядке и распараллеливать: $\mathrm{fold}$ по моноиду корректен независимо от группировки.

↦Функтор

Структура, по которой можно отобразить, сохранив форму: $\mathrm{map}(f)\circ\mathrm{map}(g)=\mathrm{map}(f\circ g)$. Так композиция переносится внутрь контейнеров.

Это и есть мост из главы об алгебре: структуры и их законы — не абстракция ради абстракции, а гарантия, что большая программа собирается из малых частей без сюрпризов. Углубление: алгебраические типы данныхчитать →

№ 05Итог

Язык, на котором можно доказывать

Мы сменили оптику: программа — не последовательность изменений, а композиция преобразований. Чистота даёт локальное рассуждение, неизменяемость убирает скрытые мутации, композиция собирает сложное из простого, λ-исчисление даёт основание, а алгебраические законы — гарантии. Это и есть «мир как преобразование».

Но локального рассуждения мало для полной уверенности. Чтобы программа была гарантированно верной, нужны типы, тотальность и доказательства. Об этом — следующая глава: математически корректные программы.

Только с алгеброй начинается строгое математическое учение. — Н. И. Лобачевский
→
Дальше по курсу

К доказуемой корректности

Продолжение темы Функционального Программирования и корректности: от чистоты — к типам и доказательствам.

№ 07Материалы

Опоры