Бесточечная нотация
Композиция функций часто используется и для так называемого бесточечного стиля записи функций. Возьмём, для примера, функцию, которую мы написали ранее:
sum' :: (Num a) => [a] –> a
sum' xs = foldl (+) 0 xs
Образец xs представлен дважды с правой стороны. Из–за каррирования мы можем пропустить образец xs с обеих сторон, так как foldl (+) 0 создаёт функцию, которая принимает на вход список. Если мы запишем эту функцию как sum' = foldl (+) 0, такая запись будет называться бесточечной. А как записать следующее выражение в бесточечном стиле?
fn x = ceiling (negate (tan (cos (max 50 x))))
Мы не можем просто избавиться от образца x с обеих правых сторон выражения. Образец x в теле функции заключён в скобки. Выражение cos (max 50) не будет иметь никакого смысла. Вы не можете взять косинус от функции! Всё, что мы можем сделать, – это выразить функцию fn в виде композиции функций.
fn = ceiling . negate . tan . cos . max 50
Отлично! Во многих случаях бесточечная запись легче читается и более лаконична; она заставляет думать о функциях, о том, как их соединение порождает результат, а не о данных и способе их передачи. Можно взять простые функции и использовать композицию как «клей» для создания более сложных. Однако во многих случаях написание функций в бесточечном стиле может делать код менее «читабельным», особенно если функция слишком сложна. Вот почему я не рекомендую создавать длинные цепочки функций, хотя меня частенько обвиняли в пристрастии к композиции. Предпочитаемый стиль – использование выражения let для присвоения меток промежуточным результатам или разбиение проблемы на подпроблемы и их совмещение таким образом, чтобы функции имели смысл для того, кто будет их читать, а не представляли собой огромную цепочку композиций.
Ранее в этой главе мы решали задачу, в которой требовалось найти сумму всех нечётных квадратов меньших 10 000. Вот как будет выглядеть решение, если мы поместим его в функцию:
oddSquareSum :: Integer
oddSquareSum = sum (takeWhile (<10000) (filter odd (map ( 2) [1..])))
Со знанием композиции функций этот код можно переписать так:
oddSquareSum :: Integer
oddSquareSum = sum . takeWhile (<10000) . filter odd $ map ( 2) [1..]
Всё это на первый взгляд может показаться странным, но вы быстро привыкнете. В подобных записях меньше визуального «шума», поскольку мы убрали все скобки. При чтении такого кода можно сразу сказать, что filter odd применяется к результату map ( 2) [1..], что затем применяется takeWhile (<10000), а функция sum суммирует всё, что получилось в результате.