Что такое функциональное программирование и почему оно похоже на математику
Программа без присваиваний, без изменяемых переменных и почти без побочных эффектов — звучит как ересь? Это функциональный подход. Он странный на первый взгляд, но именно поэтому надёжен и любим там, где важна предсказуемость.
Что, если писать программу не как список команд «сделай это, потом то», а как набор математических функций, которые просто превращают одно в другое?
Функциональное программирование строится на чистых функциях и неизменяемых данных: одинаковый вход всегда даёт одинаковый выход, и ничего не меняется исподтишка.
Привычный способ: командуем шаг за шагом
Большинство учится программировать в императивном стиле: программа — это последовательность приказов. «Заведи счётчик. Прибавь к нему единицу. Измени этот список. Перезапиши эту переменную». Мы управляем состоянием, постоянно его меняя. Это естественно, но у такого подхода есть коварная цена: когда много кусков кода меняют одни и те же данные, становится трудно понять, кто, когда и что сломал.
Функциональный способ: превращаем, а не меняем
Функциональное программирование смотрит иначе. Программа здесь — это композиция функций в математическом смысле: функция получает вход и возвращает выход, не трогая ничего вокруг. Вместо «измени список» — «создай новый список на основе старого».
Два кита подхода
Чистые функции
Функция называется чистой, если она удовлетворяет двум условиям: при одинаковых аргументах всегда возвращает одинаковый результат и не имеет побочных эффектов — не меняет внешние переменные, не пишет в файл, не лезет в сеть. Чистая функция как формула: $f(x) = x^2$ всегда даёт одно и то же и ничего не портит вокруг.
# Чистая: зависит только от входа, ничего не меняет снаружи
def double(x):
return x * 2
# Не чистая: лезет к внешней переменной и меняет её
total = 0
def add_to_total(x):
global total
total += x # побочный эффектПрелесть чистых функций — их легко тестировать и невозможно «случайно сломать соседа»: они изолированы.
Неизменяемость
Второй принцип — неизменяемые данные (immutability). Раз созданное значение не переписывается. Нужны изменения — создаём новую копию с правками, оставляя оригинал нетронутым. Это звучит расточительно, но избавляет от целого класса ошибок, когда данные неожиданно меняются «под ногами» у другого участка программы.
Функции как полноценные значения
В функциональных языках функцию можно передать в другую функцию, вернуть из неё и хранить в переменной — наравне с числами и строками. Это открывает элегантные приёмы: например, map применяет функцию к каждому элементу, а filter отбирает по условию.
numbers = [1, 2, 3, 4, 5]
squares = list(map(lambda x: x * x, numbers))
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(squares) # [1, 4, 9, 16, 25]
print(evens) # [2, 4]Где это живёт
Чисто функциональные языки — это Haskell, Elixir, Clojure. Но идеи подхода давно просочились в мейнстрим: map, filter, неизменяемые структуры и лямбды есть в Python, JavaScript, Java. Многие пишут в смешанном стиле, беря функциональную дисциплину там, где важна надёжность.
| Императивный стиль | Функциональный стиль |
| Меняет состояние | Создаёт новые значения |
| Команды по шагам | Композиция функций |
| Побочные эффекты обычны | Стремится их избегать |
Зачем это вам
Функциональное мышление делает код предсказуемым: если функция чистая, достаточно посмотреть на её вход, чтобы понять выход. Это особенно ценно в многопоточных программах, где несколько частей работают одновременно: раз данные неизменны, им нечего делить и нечего ломать. Даже если вы не уйдёте в Haskell, привычка писать маленькие чистые функции сделает любой ваш код яснее.