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

Способ 4. Лямбда функция (анонимная функция):

vectlen = \ x y -> sqrt (x*x + y*y)

Вот, собственно, выражение справа от равенства и есть лямбда-функция. Купированный значок лямбды, за которым перечисляются параметры, и после стилизованной стрелки – единственное выражение от параметров. Обратите внимание, что мы одновременно используем и упомянутый выше третий вариант – построенной анонимной функции дается имя vectl. Зачем, спросите вы – если можно написать обычную функцию:

vectlen x y = sqrt (x*x + y*y)

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

Способ 5. Частичное применение функции:

Частичное применение функции – одна из самых мощных концепций функционального программирования, один из тех столпов, на которых стоит восхитительно красивое здание. Самое время нам опять вернуться к нашим ящикам на колесиках.

Представим, что у нас есть ящик с двумя входными отверстиями и одним выходным. Мы можем положить в каждое отверстие по числу, они закроются, и из выходного отверстия выкатится, например, их сумма:

(+) :: Num a => a -> a -> a

А теперь давайте повторим эксперимент, но только пока положим в первое отверстие число: отверстие закроется, и наш ящик будет ждать второе число. А мы сделаем паузу и посмотрим, что же у нас получилось на текущий момент:

(+) 1 :: Num a => a -> a

Пока у нас нет второго числа, мы стоим перед ящиком, у которого только одно входное отверстие и одно выходное! Отдав функции двух аргументов только один из них, мы получили функцию одного аргумента!

Это видно даже просто на картинке: представьте себе функцию, имеющую тип a -> a -> a, а потом отдайте ей один требуемый a, что у нас остается, когда первый параметр a исчезнет вместе со своей стрелочкой? Правильно, останется a -> a, что это за зверь? То-то же.

Такая ситуация, когда функции N аргументов подаются M аргументов (M<N) и в результате получается функция N-M аргументов, называется частичным применением функции, или построением остаточной процедуры.

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

inc = (+) 1

Отметим еще раз: того же самого результата можно было добиться и без частичного применения:

inc x = (+) 1 x

Но мы же хотим научиться думать на уровне функций, а не на уровне данных, правда? К тому же, выражение (+) 1 x имеет тип Num a => a, и мы не сможем вставить его туда, где понадобится вставить функцию. А выражение (+) 1 имеет тип Num a => a -> a, то есть является функцией и может быть использовано везде, где нужна функция.

Отмечу еще, что частичное применение операторов называется сечением, и имеет важную особенность. Вообще-то, если какая-нибудь обычная функция f принимает параметры x y и z, то ей можно передать только x, или передать только x и y, или передать все параметры сразу – но нельзя, например, передать только y.

А вот сечениями все проще: (^2) есть функция, возводящая в квадрат, а (2^) есть функция, вычисляющая 2 в заданной степени, и отличаются эти функции тем, какой именно из параметров функции возведения в степень (^) :: => Double -> Integer -> Double мы передаем: первый или второй.

Давайте еще раз вернемся к функции (+):

(+) :: Num a => a -> a -> a

Что мы получаем, передавая функции (+) только один аргумент? Функцию от оставшегося аргумента. Это означает, что тип функции (+) можно рассматривать не только так, как мы написали, но и по-другому:

(+) :: Num a => a -> (a -> a)

В данном случае мы явно выделяем тот факт, что (+) на самом деле является функцией одного аргумента! Просто вызывая (+) с двумя параметрами мы не успеваем заметить ту стадию вычислений, на которой появляется функция одного аргумента. Но на самом деле она есть:

(+) 2 3 → (2+) 3 → (2+3) → 5

Более того, получается, что на любую функцию f :: a -> b -> с -> d -> … -> y -> z можно посмотреть как на функцию f :: a -> (b -> (с -> (d -> … -> (y -> z)…). Это означает, что абсолютно все функции в Haskell имеют ровно один параметр (и один результат, который может быть чем угодно – в том числе и опять функцией). Ничего себе открытие?

Такие функции называются "каррированными", по имени все того же великого и ужасного Хаскеля Карри (а правильнее было бы называть их – "шонфинкелированные", по имени их первооткрывателя – Моисея Эльевича Шейнфинкеля).

В отличие от обычных, например, функций языка C++, где все функции, наоборот, некаррированные. В упомянутом C++ даже оператор сложения имеет тип примерно такой: (+) :: (Double,Double) -> Double, принимая кортеж и возвращая число.

Понимаете теперь, почему в C++ вы были обязаны писать f(x,y), например? Потому что функции некаррированные, и принимают кортеж.

А в случае каррированной функции в Haskell мы обязаны писать f x y, и не можем писать f (x,y), потому что функция принимает параметр x, а не кортеж (x,y).