Point-free стиль и читаемость

Point-free («бесточечный») стиль — это запись функции через композицию, без явного упоминания аргументов. Иногда красиво, иногда чересчур.
sumDoubled xs = sum (map (* 2) xs) можно записать как sumDoubled = sum . map (* 2). Аргумент xs исчез — но смысл остался.

Point-free стиль (его ещё в шутку зовут «pointless») — это определение функции без явного указания её аргументов, целиком через композицию и частичное применение. Имя «бесточечный» — про отсутствие «точек», то есть конкретных значений-аргументов.

Как убрать аргумент

Если функция просто передаёт свой аргумент в цепочку, его можно опустить:

-- с аргументом (pointful):
sumDoubled :: [Int] -> Int
sumDoubled xs = sum (map (* 2) xs)

-- без аргумента (point-free):
sumDoubled' :: [Int] -> Int
sumDoubled' = sum . map (* 2)

Обе версии делают одно и то же. Во второй xs исчезает, потому что функция определена как композиция: «применить map (* 2), затем sum». Это опирается на каррирование и композицию из предыдущих уроков.

pointful:    f xs = g (h xs)
point-free:  f    = g . h
             (аргумент xs «сокращается»)

Ещё примеры:

countEvens :: [Int] -> Int
countEvens = length . filter even      -- вместо  \xs -> length (filter even xs)

shout :: String -> String
shout = (++ "!") . map toUpper          -- в верхний регистр, затем "!"

Когда стиль вредит

Бесточечность хороша для коротких конвейеров, но превращается в ребус, когда цепочка длинная или содержит хитрые перестановки аргументов. Сравните:

-- читаемо:
avg xs = sum xs `div` length xs

-- бесточечно, но загадочно:
-- avg = (. length) . div . sum  -- НЕ делайте так

В Python чистый point-free неудобен из-за отсутствия каррирования, но идею «функция как композиция без явного аргумента» можно показать:

# Та же идея на Python: композиция вместо явного аргумента
def compose(*funcs):
    def run(x):
        for f in reversed(funcs):
            x = f(x)
        return x
    return run

# sum_doubled = sum . map (*2)  (концептуально)
sum_doubled = compose(sum, lambda xs: [x * 2 for x in xs])
print(sum_doubled([1, 2, 3]))   # 12

count_evens = compose(len, lambda xs: [x for x in xs if x % 2 == 0])
print(count_evens([1, 2, 3, 4, 5, 6]))   # 3

Элегантность с чувством меры

Бесточечный стиль — это эстетика Haskell в концентрированном виде, и именно поэтому к нему стоит относиться с осторожностью. Для коротких очевидных конвейеров вроде length . filter even он великолепен: убирает шум, оставляет чистый поток данных, читается почти как определение из учебника. Но соблазн «убрать все аргументы любой ценой» — ловушка. Как только в цепочке появляются перестановки аргументов, flip и хитрые секции операторов, бесточечная запись превращается в головоломку, разгадывать которую дольше, чем читать обычную версию с явным аргументом. Опытный разработчик выбирает не самую короткую, а самую понятную форму. Хорошее правило: если, глядя на point-free определение, вы на секунду задумались «а что тут вообще происходит?» — верните аргумент. Код пишут для людей, и читаемость почти всегда важнее минимизации символов.

Как это мыслить

Спросите себя: «функция просто пропускает аргумент через конвейер?» Если да и цепочка короткая — бесточечная запись делает определение чище, подчёркивая поток данных. Если же приходится жонглировать аргументами или цепочка длинная, явные аргументы понятнее. Читаемость важнее краткости.

Любопытно, что само сообщество Haskell относится к бесточечному стилю с долей самоиронии — недаром его в шутку зовут «pointless», то есть «бессмысленный». Это напоминание не воспринимать краткость как самоцель. Бесточечная запись хороша ровно настолько, насколько она проясняет намерение, а не прячет его. Если определение читается как чистый поток данных — прекрасно; если же оно требует мысленно разворачивать цепочку композиций и перестановок, то явные аргументы выигрывают. Опытные разработчики свободно переключаются между стилями в зависимости от ситуации, а не следуют догме. Хороший ориентир — представить коллегу, который впервые открывает ваш код: какая из двух форм будет понятна ему быстрее? Именно её и стоит выбрать, потому что код читают многократно, а пишут однажды.

Частые ошибки

  • Гнаться за бесточечностью любой ценой. Слишком умная цепочка читается хуже простой версии с аргументом.
  • Путаться в порядке композиции. Помните: правая функция работает первой.
  • Применять к функциям с переставленными аргументами. Это требует flip и быстро запутывает.

Best practices

  • Используйте point-free для коротких, очевидных конвейеров: length . filter even.
  • Если возникает соблазн написать (. f) . g . flip ... — остановитесь и верните аргумент.
  • Оптимизируйте на читаемость для человека, а не на минимум символов.

Итог. Point-free стиль определяет функции через композицию без явных аргументов. Для коротких конвейеров он элегантен и подчёркивает поток данных, но злоупотребление превращает код в загадку. Выбирайте ту форму, которую легче читать.

Проверьте себя
1. Как записать sumDoubled xs = sum (map (* 2) xs) в point-free виде?
AsumDoubled = map (* 2) . sum
BsumDoubled = sum . map (* 2)
CsumDoubled = sum + map (* 2)
DsumDoubled xs = xs
2. Когда point-free стиль скорее вредит?
AКогда конвейер короткий и очевидный
BКогда цепочка длинная и требует перестановки аргументов, что делает её нечитаемой
CКогда используется map
DНикогда — он всегда лучше