Композиция функций

Композиция (.) склеивает две функции в одну: «сначала одна, потом другая». Так из мелких функций собирают большие — как из деталей.
(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), а $ применяет функцию без скобок. Вместе они позволяют собирать сложное поведение из простых функций — это и есть функциональный стиль во всей красе.

Проверьте себя
1. Чему равно (f . g) x ?
Ag (f x)
Bf (g x) — сначала g, затем f
Cf x + g x
Dx
2. Для чего удобен оператор $ ?
AДля умножения
BЧтобы применять функцию к аргументу без лишних скобок
CЧтобы объявить переменную
DЧтобы создать список