Немного о высоких материях
Функции могут принимать функции в качестве параметров и возвращать функции в качестве значений. Чтобы проиллюстрировать это, мы собираемся создать функцию, которая принимает функцию, а затем дважды применяет её к чему-нибудь!
applyTwice :: (a –> a) –> a –> a
applyTwice f x = f (f x)
Прежде всего, обратите внимание на объявление типа. Раньше мы не нуждались в скобках, потому что символ –> обладает правой ассоциативностью. Однако здесь скобки обязательны. Они показывают, что первый параметр – это функция, которая принимает параметр некоторого типа и возвращает результат того же типа. Второй параметр имеет тот же тип, что и аргумент функции – как и возвращаемый результат. Мы можем прочитать данное объявление в каррированном стиле, но, чтобы избежать головной боли, просто скажем, что функция принимает два параметра и возвращает результат. Первый параметр – это функция (она имеет тип a –> a), второй параметр имеет тот же тип a. Заметьте, что совершенно неважно, какому типу будет соответствовать типовая переменная a – Int, String или вообще чему угодно – но при этом все значения должны быть одного типа.
ПРИМЕЧАНИЕ. Отныне мы будем говорить, что функция принимает несколько параметров, вопреки тому что в действительности каждая функция принимает только один параметр и возвращает частично применённую функцию. Для простоты будем говорить, что a –> a –> a принимает два параметра, хоть мы и знаем, что происходит «за кулисами».
Тело функции applyTwice достаточно простое. Мы используем параметр f как функцию, применяя её к параметру x (для этого разделяем их пробелом), после чего передаём результат снова в функцию f. Давайте поэкспериментируем с функцией:
ghci> applyTwice (+3) 10
16
ghci> applyTwice (++ " ХА-ХА") "ЭЙ"
"ЭЙ ХА-ХА ХА-ХА"
ghci> applyTwice ("ХА-ХА " ++) "ЭЙ"
"ХА-ХА ХА-ХА ЭЙ"
ghci> applyTwice (multThree 2 2) 9
144
ghci> applyTwice (3:) [1]
[3,3,1]
Красота и полезность частичного применения очевидны. Если наша функция требует передать ей функцию одного аргумента, мы можем частично применить функцию-параметр таким образом, чтобы оставался неопределённым всего один параметр, и затем передать её нашей функции. Например, функция + принимает два параметра; с помощью сечений мы можем частично применить её так, чтобы остался только один.