Элемент с номером четыре из списка содержит функцию, которая выполняет умножение на четыре – (4*)
. Затем мы применяем значение 5
к этой функции. Это то же самое, что записать (4*) 5
или просто 4 * 5
.
Лямбда-выражения
\
(напоминающий, если хорошенько напрячь воображение, греческую букву лямбда – λ), затем записываем параметры, разделяя их пробелами. Далее пишем знак –>
и тело функции. Обычно мы заключаем лямбду в круглые скобки, иначе она продолжится до конца строки вправо.
Если вы обратитесь к примеру, приведённому в предыдущем разделе, то увидите, что мы создали функцию isLong
в секции where
функции numLongChains
только для того, чтобы передать её в фильтр. Вместо этого можно использовать анонимную функцию:
numLongChains :: Int
numLongChains = length (filter (\xs –> length xs > 15) (map chain [1..100]))
Анонимные функции являются выражениями, поэтому мы можем использовать их таким способом, как в примере. Выражение (\xs –> length xs > 15)
возвращает функцию, которая говорит нам, больше ли 15
длина переданного списка.
Те, кто не очень хорошо понимает, как работает каррирование и частичное применение функций, часто используют анонимные функции там, где не следует. Например, выражения map (+3) [1,6,3,2]
и map (\x –> x + 3) [1,6,3,2]
эквивалентны, так как (+3)
и (\x –> x + 3)
– это функции, которые добавляют тройку к аргументу. Излишне говорить, что использование анонимной функции в этом случае неоправданно, так как частичное применение значительно легче читается.
Как и обычные функции, лямбда-выражения могут принимать произвольное количество параметров:
ghci> zipWith (\a b –> (a * 30 + 3) / b) [5,4,3,2,1] [1,2,3,4,5]
[153.0,61.5,31.0,15.75,6.6]
По аналогии с обычными функциями, можно выполнять сопоставление с образцом в лямбда-выражениях. Единственное отличие в том, что нельзя определить несколько образцов для одного параметра – например, записать для одного параметра образцы []
и (x: xs)
и рассчитывать, что выполнение перейдёт к образцу (x:xs)
в случае неудачи с []
. Если сопоставление с образцом в анонимной функции заканчивается неудачей, происходит ошибка времени выполнения, так что поосторожнее с этим!
ghci> map (\(a,b) –> a + b) [(1,2),(3,5),(6,3),(2,6),(2,5)]
[3,8,9,8,7]
Обычно анонимные функции заключаются в круглые скобки, если только мы не хотим, чтобы лямбда-выражение заняло всю строку. Интересная деталь: поскольку все функции каррированы по умолчанию, допустимы две эквивалентные записи.
addThree :: Int -> Int -> Int -> Int
addThree x y z = x + y + z
addThree' :: Int -> Int -> Int -> Int
addThree' = \x -> \y -> \z -> x + y + z
Если мы объявим функцию подобным образом, то станет понятно, почему декларация типа функции представлена именно в таком виде. И в декларации типа, и в теле функции имеются три символа –>
. Конечно же, первый способ объявления функций значительно легче читается; второй – это всего лишь очередная возможность продемонстрировать каррирование.
ПРИМЕЧАНИЕ. Обратите внимание на то, что во втором примере анонимные функции не заключены в скобки. Когда вы пишете анонимную функцию без скобок, предполагается, что вся часть после символов –>
относится к этой функции. Так что пропуск скобок экономит на записи. Конечно, ничто не мешает использовать скобки, если это вам больше нравится.
Тем не менее есть случаи, когда использование такой нотации оправдано. Я думаю, что функция flip
будет лучше читаться, если мы объявим её так:
flip' :: (a –> b –> c) –> b –> a –> c
flip' f = \x y –> f y x
Несмотря на то что эта запись равнозначна flip' f x y = f y x
, мы даём понять, что данная функция чаще всего используется для создания новых функций. Самый распространённый сценарий использования flip
– вызов её с некоторой функцией и передача результирующей функции в map
или zipWith
: