Haskell из лямбда-исчисления. Функции строятся с помощью специальных конструкций, которые называ-
ются лямбда-функциями. По сути лямбда-функции являются безымянными функциями. Давайте посмотрим
на лямбда функцию, которая прибавляет к аргументу единицу:
x -> x + 1
Для того, чтобы превратить лямбда-функцию в обычную функцию мысленно замените знак на имя
noName, а стрелку на знак равно:
noName x = x + 1
Мы получили обычную функцию Haskell, с такими мы уже много раз встречались. Зачем специальный
синтаксис для определения безымянных функций? Ведь можно определить её в виде уравнений. К тому же
кому могут понадобиться безымянные функции? Ведь смысл функции в том, чтобы выделить определённый
шаблон поведения и затем ссылаться на него по имени функции.
Смысл безымянной функции в том, что ею, также как и любым другим элементом композиционного
стиля, можно пользоваться в любой части обычных выражений. С её помощью мы можем создавать функции
“на лету”. Предположим, что мы хотим профильтровать список чисел, мы хотим выбрать из них лишь те, что
меньше 10, но больше 2, и к тому же они должны быть чётными. Мы можем написать:
f :: [Int] -> [Int]
f = filter p
where p x = x > 2 && x < 10 && even x
При этом нам приходится давать какое-нибудь имя предикату, например p. С помощью безымянной функ-
ции мы могли бы написать так:
f :: [Int] -> [Int]
f = filter (x -> x > 2 && x < 10 && even x)
Смотрите мы составили предикат сразу в аргументе функции filter. Выражение (x -> x > 2 && x <
10 && even x) является обычным значением.
Возможно у вас появился вопрос, где аргумент функции? Где тот список по которому мы проводим филь-
трацию. Ответ на этот вопрос кроется в частичном применении. Давайте вычислим по правилу применения
тип функции filter:
f :: (a -> Bool) -> [a] -> [a],
x :: (Int -> Bool)
——————————————————
(f x) :: [Int] -> [Int]
После применения параметр a связывается с типом Int, поскольку при применении происходит сопостав-
ление более общего предиката a -> Bool из функции filter с тем, который мы передали первым аргументом
Int -> Bool. После этого мы получаем тип (f x) :: [Int] -> [Int] это как раз тип функции, которая прини-
мает список целых чисел и возвращает список целых чисел. Частичное применение позволяет нам не писать
в таких выражениях:
f xs = filter p xs
where p x = …
последний аргумент xs.
К примеру вместо
Определение функций | 65
add a b = (+) a b
мы можем просто написать:
add = (+)
Такой стиль определения функций называют бесточечным (point-free).
Давайте выразим функцию filter с помощью лямбда-функций:
filter :: (a -> Bool) -> ([a] -> [a])
filter = p -> xs -> case xs of
[]
-> []
(x:xs) -> let rest = filter p xs
in
if
p x
then x : rest
else rest
Мы определили функцию filter пользуясь только элементами композиционного стиля. Обратите внима-
ние на скобки в объявлении типа функции. Я хотел напомнить вам о том, что все функции в Haskell являются
функциями одного аргумента. Это определение функции filter как нельзя лучше подчёркивает этот факт.
Мы говорим, что функция filter является функцией одного аргумента p в выражении p -> , которая возвра-
щает также функцию одного аргумента. Мы выписываем это в явном виде в выражении xs -> . Далее идёт
выражение, которое содержит определение функции.
Отметим, что лямбда функции могут принимать несколько аргументов, в предыдущем определении мы
могли бы написать:
filter :: (a -> Bool) -> ([a] -> [a])
filter = p xs -> case xs of
…
но это лишь синтаксический сахар, который разворачивается в предыдущую запись.
Для тренировки определим несколько стандартных функций для работы с кортежами с помощью лямбда-
функций (все они определены в Prelude):
fst :: (a, b) -> a
fst = (a, _) -> a
snd :: (a, b) -> b
snd = (_, b) -> b
swap :: (a, b) -> (b, a)
swap = (a, b) -> (b, a)
Обратите внимание на то, что все функции словно являются константами. Они не содержат аргументов.
Аргументы мы “пристраиваем” с помощью безымянных функций.
Определим функции преобразования первого и второго элемента кортежа (эти функции определены в
модуле Control.Arrow)
first :: (a -> a’) -> (a, b) -> (a’, b)
first = f (a, b) -> (f a, b)
second :: (b -> b’) -> (a, b) -> (a, b’)
second = f (a, b) -> (a, f b)
Также в Prelude есть полезные функции, которые превращают функции с частичным применением в
обычны функции и наоборот:
curry :: ((a, b) -> c) -> a -> b -> c
curry = f -> a -> b -> f (a, b)
uncurry :: (a -> b -> c) -> ((a, b) -> c)
uncurry = f -> (a, b) -> f a b
66 | Глава 4: Декларативный и композиционный стиль
Функция curry принимает функцию двух аргументов для которой частичное применение невозможно.
Это имитируется с помощью кортежей. Функция принимает кортеж из двух элементов. Функция curry (от
слова каррирование, частичное применение) превращает такую функцию в обычную функцию Haskell. А
функция uncurry выполняет обратное преобразование.
С помощью лямбда-функций можно имитировать локальные переменные. Так например можно перепи-