haskell-notes

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 выполняет обратное преобразование.

С помощью лямбда-функций можно имитировать локальные переменные. Так например можно перепи-

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162