Композиция функций
Композиция (.) склеивает две функции в одну: «сначала одна, потом другая». Так из мелких функций собирают большие — как из деталей.
(f . g) xозначаетf (g x): сначала срабатываетg, её результат идёт вf. Это конвейер, записанный одним символом.
Композиция функций — фундамент функционального стиля. Оператор . (точка) соединяет две функции так, что выход одной становится входом другой:
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x == f (g x)
f . g
x --> [ g ] --> [ f ] --> результат
| |
сначала потом
(f . g) x = f (g x)
Пример: построим функцию, которая удваивает и затем прибавляет один, из двух простых:
double :: Int -> Int
double x = x * 2
inc :: Int -> Int
inc x = x + 1
doubleThenInc :: Int -> Int
doubleThenInc = inc . double -- сначала double, потом inc
-- doubleThenInc 5 == inc (double 5) == inc 10 == 11
Композицию читают справа налево: inc . double — «сначала double, потом inc». Можно сцеплять сколько угодно функций в длинный конвейер:
process :: [Int] -> Int
process = sum . map (* 2) . filter even
-- отфильтровать чётные, удвоить, сложить
Оператор применения $
Близкий помощник — оператор $, который применяет функцию к аргументу, но с низким приоритетом. Он избавляет от скобок:
-- вместо print (sum (map (*2) [1,2,3]))
result = print $ sum $ map (* 2) [1, 2, 3]
$ говорит «возьми всё справа как один аргумент» — удобно, чтобы не громоздить вложенные скобки.
В Python нет встроенного оператора композиции, но идею легко собрать вручную:
# Та же идея на Python: композиция функций
def compose(f, g):
return lambda x: f(g(x)) # (f . g) x = f(g(x))
double = lambda x: x * 2
inc = lambda x: x + 1
double_then_inc = compose(inc, double)
print(double_then_inc(5)) # 11
# конвейер: фильтр -> удвоить -> сумма
data = [1, 2, 3, 4, 5, 6]
print(sum(map(lambda x: x * 2, filter(lambda x: x % 2 == 0, data)))) # 24
Программа как трубопровод
Композиция функций превращает программирование в сборку конвейера из готовых деталей. Вместо промежуточных переменных вы соединяете маленькие функции в одну цепочку, где данные текут от входа к выходу: sum . map (* 2) . filter even — это законченный обработчик, собранный из трёх простых кусочков. Такой стиль не только короче, но и расширяется без боли: понадобился новый шаг — допишите функцию в цепочку, и всё. Оператор $ — верный спутник композиции: он убирает вложенные скобки, позволяя писать print $ sum $ map (* 2) xs вместо нагромождения круглых скобок. Главное — не забывать, что композиция читается справа налево: первой срабатывает самая правая функция. И помнить про меру: если конвейер стал нечитаемым, не стыдно вернуть промежуточные имена через where — читаемость важнее эффектной краткости.
Как это мыслить
Стройте программы как трубопровод: маленькие функции — это секции трубы, композиция соединяет их. Вместо того чтобы писать промежуточные переменные, вы описываете поток данных «слева направо по смыслу» (справа налево по записи). Это делает код декларативным и легко расширяемым: добавить шаг — дописать одну функцию в цепочку.
Полезно сопоставить композицию с оператором $, ведь новички их часто путают. Точка . соединяет две функции в новую функцию, ничего пока не вычисляя: результат — снова функция, ждущая аргумент. Доллар $ же немедленно применяет функцию слева к значению справа. Грубо говоря, . строит конвейер, а $ запускает по нему конкретные данные. Часто их используют вместе: цепочку собирают композицией, а в конце применяют к аргументу через $, избегая скобок. Понимание этой разницы снимает добрую половину путаницы у тех, кто только осваивает функциональный стиль. И помните общее правило обоих операторов: композиция и доллар существуют ради читаемости, поэтому, если они её ухудшают, лучше вернуть явные скобки или промежуточные имена.
Частые ошибки
- Перепутать порядок. В
f . gпервой работаетg(правая), а неf. - Несовпадение типов на стыке. Выход
gобязан подходить на входf. - Путать
.и$..соединяет функции,$применяет функцию к значению.
Best practices
- Собирайте логику из маленьких функций через композицию — это «point-free» стиль.
- Используйте
$, чтобы убрать лишние скобки в цепочках. - Не увлекайтесь: если конвейер стал нечитаемым, верните промежуточные имена через
where.
Итог. Композиция (.) склеивает функции в конвейер, где (f . g) x = f (g x), а $ применяет функцию без скобок. Вместе они позволяют собирать сложное поведение из простых функций — это и есть функциональный стиль во всей красе.