sum $ filter (> 10) $ map (*2) [2..10]
Но кроме избавления от скобок оператор $
означает, что само применение функции может использоваться как и любая другая функция. Таким образом, мы можем, например, применить функцию к списку функций:
ghci> map ($ 3) [(4+), (10*), ( 2), sqrt]
[7.0,30.0,9.0,1.7320508075688772]
Функция ($ 3)
применяется к каждому элементу списка. Если задуматься о том, что она делает, то окажется, что она берёт функцию и применяет её к числу 3
. Поэтому в данном примере каждая функция из списка применится к тройке, что, впрочем, и так очевидно.
Композиция функций
В математике композиция функций определяется следующим образом:
(
Это значит, что композиция двух функций создаёт новую функцию, которая, когда её вызывают, скажем, с параметром
В языке Haskell композиция функций понимается точно так же. Мы создаём её при помощи оператора (.)
, который определён следующим образом:
(.) :: (b –> c) –> (a –> b) –> a –> c
f . g = \x –> f (g x)
По декларации типа функция f
должна принимать параметр того же типа, что и результат функции g
. Таким образом, результирующая функция принимает параметр того же типа, что и функция g
, и возвращает значение того же типа, что и функция f
. Выражение negate . (* 3)
возвращает функцию, которая принимает число, умножает его на три и меняет его знак на противоположный.
Одно из применений композиции функций – это создание функций «на лету» для передачи их другим функциям в качестве параметров. Конечно, мы можем использовать для этого анонимные функции, но зачастую композиция функций понятнее и лаконичнее. Допустим, что у нас есть список чисел и мы хотим сделать их отрицательными. Один из способов сделать это – получить абсолютное значение числа (модуль), а затем перевести его в отрицательное, вот так:
ghci> map (\x –> negate (abs x)) [5,–3,–6,7,–3,2,–19,24]
[–5,–3,–6,–7,–3,–2,–19,–24]
Обратите внимание на анонимную функцию и на то, как она похожа на результирующую композицию функций. А вот что выйдет, если мы воспользуемся композицией:
ghci> map (negate . abs) [5,–3,–6,7,–3,2,–19,24]
[–5,–3,–6,–7,–3,–2,–19,–24]
Невероятно! Композиция функций правоассоциативна, поэтому у нас есть возможность включать в неё много функций за один раз. Выражение f (g (z x))
эквивалентно (f . g . z) x
. Учитывая это, мы можем превратить
ghci> map (\xs –> negate (sum (tail xs))) [[1..5],[3..6],[1..7]]
[–14,–15,–27]
в
ghci> map (negate . sum . tail) [[1..5],[3..6],[1..7]]
[–14,–15,–27]
Функция negate . sum . tail
принимает список, применяет к нему функцию tail
, суммирует результат и умножает полученное число на -1
. Получаем точный эквивалент анонимной функции из предыдущего примера.
Композиция функций с несколькими параметрами
Ну а как насчёт функций, которые принимают несколько параметров? Если мы хотим использовать их в композиции, обычно мы частично применяем их до тех пор, пока не получим функцию, принимающую только один параметр. Запись
sum (replicate 5 (max 6.7 8.9))
может быть преобразована так:
(sum . replicate 5) (max 6.7 8.9)
или так:
sum . replicate 5 $ max 6.7 8.9
Функция replicate 5
применяется к результату вычисления max 6.7 8.9
, после чего элементы полученного списка суммируются. Обратите внимание, что функция replicate
частично применена так, чтобы у неё остался только один параметр, так что теперь результат max 6.7 8.9
передаётся на вход replicate 5
; новым результатом оказывается список чисел, который потом передаётся функции sum
.
Если вы хотите переписать выражение с кучей скобок, используя функциональную композицию, можно сначала записать самую внутреннюю функцию с её параметрами, затем поставить перед ней знак $
, а после этого пристраивать вызовы всех других функций, записывая их без последнего параметра и разделяя точками. Например, выражение
replicate 2 (product (map (*3) (zipWith max [1,2] [4,5])))
можно переписать так:
replicate 2 . product . map (*3) $ zipWith max [1,2] [4,5]