Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
MakeevGA-Haskell-a4-shortcode_2014_05_31.doc
Скачиваний:
15
Добавлен:
19.01.2023
Размер:
1.79 Mб
Скачать

Композиция функций

Давайте еще раз посмотрим на последнюю функцию, которая выбрасывала из списка строк пустые:

nonEmptyStrings = filter (\s -> not (null s))

Что если нам наоборот нужно было оставлять только пустые, а выбрасывать все остальные? Такая функция записалась бы еще проще:

emptyStrings = filter null

Почему с непустыми строками приходится использовать лямбду? Потому что нам нужна не функция null, а функция противоположная ей: функция, которая возвращает True тогда, когда функция null возвращает False, и наоборот. Функция not превращает False в True, а True в False, но функция not не может превратить функцию null в противоположную ей! Это приходится делать с помощью лямбды: (\s -> not (null s)). Мы конструируем функцию, которой если передать строку, то она передаст ее в функцию null, потом результат функции null передаст в функцию not, и уже результат той вернет.

Такое поведение встречается очень часто: когда результат работы одной функции полностью передается на вход другой функции. В математике это называется композицией функций.

Точнее даже не так: композицией двух функций f и g называется функция, заключающаяся в последовательном применении к чему-то функции f, а затем функции g:

(.) :: (b -> c) -> (a -> b) -> a -> c

Вот она, композиция в Haskell, – функция с крайне непонятным, на первый взгляд, типом. Если присмотреться, то видно, что функция (.) принимает одну функцию типа (b -> c), еще одну функцию типа (a -> b), и возвращает функцию типа (a -> c).

Впрочем, если присмотреться по-другому, то видно, что функция (.) принимает одну функцию типа (b -> c), еще одну функцию типа (a -> b), элемент типа a и возвращает элемент типа c. Вот два эквивалентных определения функции (.), отражающих оба этих взгляда на композицию:

(.) f g = \x -> f (g x)

(.) f g x = f (g x)

С помощью композиции можно легко составлять сложные функции из двух простых. Например, если h = (+1).(^2), то h 5 → 26. Ну и если нужно вычислить функцию null, результат ее подать на вход функции not, то функция, делающая сразу и то и другое с помощью композиции записывается так: not.null.

Обращаю ваше внимание, что функция (.) работает именно с функциями, а не с данными. Она берет две функции и "состыковывает" их вместе, получая третью, сложную функцию.

Если опять представить себе две функции как передвижные ящики на колесиках, у которых есть по одному входному и одному выходному отверстию, то композицию этих функций можно получить, поставив эти два ящика рядом так, чтобы выходное отверстие одного вплотную прилегало к входному отверстию другого.

Обратите внимание, что данных на этой картинке еще нет, потому что композиция – это про функции, а не про данные. Данные могут вообще никогда не появиться, а результат работы композиции – вот он уже, готов и помаргивает огоньками.

Если вдруг когда кто-нибудь бросит что-то во входное отверстие первой функции, она сразу создаст свой выход, и он автоматически попадет на вход во вторую функцию, и преобразуется ей в что-то третье. Произойдет это без всякого вмешательства с нашей стороны, и поэтому можно будет такие два состыкованные ящика рассматривать как единый ящик, единую функцию.

Итак,

emptyStrings = filter null

nonEmptyStrings = filter (not.null)

С помощью композиции можно укорачивать запись преобразований, состоящих из нескольких этапов. Например, что если нужно каждый элемент списка увеличить на единицу, потом возвести в квадрат, а потом опять увеличить на единицу? Вот варианты:

foo1 xs = map (+1) (map (^2) (map (+1) xs))

foo2 = map (+1) . map (^2) . map (+1)

foo3 = map ((+1).(^2).(+1))

Или, например, нужно посчитать количество четных чисел в списке, не делящихся на 4:

foo1 xs = length (filter (\x -> x `mod` 2 == 0 && \x -> x `mod` 4 /= 0) xs)

foo2 = length .

filter (\x -> x `mod` 4 /= 0) .

filter (\x -> x `mod` 2 == 0)

foo3 = length .

filter ((/=0).(`mod` 4)) .

filter ((==0).(`mod` 2))

Видите, как в каких-то вариантах функций нет ни одного упоминания о данных вообще? Ни одного формального параметра – только манипуляции функциями: создание нужных функций путем частичного применения и композиции, и передача этих функций в функции высших порядков, и опять композиция...