haskell-notes

Давайте определим в терминах композиции ещё одну полезную функцию. А именно функцию примене-

ния. Вспомним её тип:

($) :: (a -> b) -> a -> b

Эту функцию можно определить через композицию, если у нас есть в наличии постоянная функция и

единичный тип. Мы будем считать, что константа это функция из единичного типа в значение. Превратив

константу в функцию мы можем составить композицию:

($) :: (a -> b) -> a -> b

f $ a = (const a >> f) ()

В самом конце мы подставляем специальное значение (). Это значение единичного типа (unit type) или

кортежа с нулём элементов. Единичный тип имеет всего одно значение, которым мы и воспользовались в

этом определении. Зачем такое запутанное определение, вместо привычного (f a)? Оказывается точно таким

же способом мы можем определить применение в нашем мире специальных функций a -> m b.

Применение в этом мире происходит особенным образом. Необходимо помнить о том, что второй аргу-

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

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

случае это m b.

Посмотрим на типы специальных функций применения:

(*$) :: (a -> m b) -> m a -> m b

(+$) :: (a -> b)

-> m a -> m b

Функция *$ применяет специальную функцию к специальному значению, а функция +$ применяет обыч-

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

применения, мы только меняем знаки для композиции:

f

$ a = (const a >> f) ()

f *$ a = (const a *> f) ()

f +$ a = (const a +> f) ()

Теперь мы можем не только нанизывать специальные функции друг на друга но и применять их к значе-

ниям. Добавим эти определения в модуль Kleisli и посмотрим как происходит применение в интерпрета-

торе. Одна тонкость заключается в том, что мы определяли применение в терминах класса Kleisli, поэтому

правильно было написать типы новых функций так:

infixr 0 +$, *$

(*$) :: Kleisli m => (a -> m b) -> m a -> m b

(+$) :: Kleisli m => (a -> b)

-> m a -> m b

Также мы определили приоритет выполнения операций.

Загрузим в интерпретатор:

*Kleisli> let three = Succ (Succ (Succ Zero))

*Kleisli> pred *$ pred *$ idK three

Just (Succ Zero)

*Kleisli> pred *$ pred *$ idK Zero

Nothing

Применение функций | 93

Обратите внимание на то как мы погружаем в мир специальных функций обычное значение с помощью

функции idK.

Вычислим третье поколение L-системы:

*Kleisli> next *$ next *$ next *$ idK ’a’

”abaab”

Мы можем использовать и другие функции на списках:

*Kleisli> next *$ tail $ next *$ reverse $ next *$ idK ’a’

”aba”

Применение функций многих переменных

С помощью функции +$ мы можем применять к специальным значениям обычные функции одного аргу-

мента. А что если нам захочется применить функцию двух аргументов?

Например если мы захотим сложить два частично определённых числа:

?? (+) (Just 2) (Just 2)

На месте ?? должна стоять функция типа:

?? :: (a -> b -> c) -> m a -> m b -> m c

Оказывается с помощью методов класса Kleisli мы можем определить такую функцию для любой обыч-

ной функции, а не только для функции двух аргументов. Мы будем называть такие функции словом liftN,

где N – число, указывающее на арность функции. Функция (liftN f) “поднимает” (от англ. lift) обычную

функцию f в мир специальных функций.

Функция lift1 у нас уже есть, это просто функция +$. Теперь давайте определим функцию lift2:

lift2 :: Kleisli m => (a -> b -> c) -> m a -> m b -> m c

lift2 f a b = …

Поскольку функция двух аргументов на самом деле является функцией одного аргумента мы можем

применить первый аргумент с помощью функции lift1, посмотрим что у нас получится:

lift1

:: (a’ -> b’) -> m’ a’ -> m’ b’

f

:: (a -> b -> c)

a

:: m a

lift1 f a

:: m (b -> c)

— m’ == m, a’ == a, b’ == b -> c

Теперь в нашем определении для lift2 появится новое слагаемое g:

lift2 :: Kleisli m => (a -> b -> c) -> m a -> m b -> m c

lift2 f a b = …

where g = lift1 f a

Один аргумент мы применили, осталось применить второй. Нам нужно составить выражение (g b), но

для этого нам нужна функция типа:

m (b -> c) -> m b -> m c

Эта функция применяет к специальному значению функцию, которая завёрнута в тип m. Посмотрим на

определение этой функции, мы назовём её $$:

($$) :: Kleisli m => m (a -> b) -> m a -> m b

mf $$ ma = ( +$ ma) *$ mf

Вы можете убедиться в том, что это определение проходит проверку типов. Посмотрим как эта функция

работает в интерпретаторе на примере частично определённых и многозначных функций, для этого давайте

добавим в модуль Kleisli это определение и загрузим его в интерпретатор:

94 | Глава 6: Функторы и монады: теория

*Kleisli> :reload Kleisli

Ok, modules loaded: Kleisli, Nat.

*Kleisli> Just (+2) $$ Just 2

Just 4

*Kleisli> Nothing $$ Just 2

Nothing

*Kleisli> [(+1), (+2), (+3)] $$ [10,20,30]

[11,21,31,12,22,32,13,23,33]

*Kleisli> [(+1), (+2), (+3)] $$ []

[]

Обратите внимание на то, что в случае списков были составлены все возможные комбинации применений.

Страницы: 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