Выбрать главу

a

f

b

Nothing

Рис. 6.2: Частично определённая функция

Частично определённая функция имеет тип a -> Maybe b (рис. 6.2), если всё в порядке и значение было

вычислено, она вернёт (Just a), а в случае ошибки будет возвращено значение Nothing. Теперь мы можем

определить нашу функцию так:

pred :: Nat -> Maybe Nat

pred Zero

= Nothing

pred (Succ a)

= Just a

Для Zero предыдущий элемент не определён .

Составляем функции вручную

Значение функции pred завёрнуто в упаковку Maybe, и для того чтобы воспользоваться им нам придётся

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

числа:

pred2 :: Nat -> Maybe Nat

pred2 x =

case pred x of

Just (Succ a) -> Just a

_

-> Nothing

Если мы захотим определить pred3, мы заменим pred в case-выражении на pred2. Вроде не такое уж и

длинное решение. Но всё же мы теряем все преимущества гибких функций, все преимущества бесточечного

стиля. Нам бы хотелось написать так:

pred2 :: Nat -> Maybe Nat

pred2 = pred >> pred

pred3 :: Nat -> Maybe Nat

pred3 = pred >> pred >> pred

Но компилятор этого не допустит.

Композиция

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

ние графически (рис. 6.3). Сверху изображены две частично определённых функции. Если функция f вернула

значение, то оно подставляется в следующую частично определённую функцию. Если же первая функция не

смогла вычислить результат и вернула Nothing, то считается что вся функция (f*> g) вернула Nothing.

Теперь давайте закодируем это определение в Haskell. При этом мы воспользуемся нашим классом

Kleisli. Аналогом функции id для частично определённых функций будет функция, которая просто за-

ворачивает значение в конструктор Just.

instance Kleisli Maybe where

idK

= Just

f *> g = \a -> case f a of

Nothing -> Nothing

Just b

-> g b

Смотрите, в case-выражении мы возвращаем Nothing, если функция f вернула Nothing, а если ей удалось

вычислить значение и она вернула (Just b) мы передаём это значение в следующую функцию, то есть

составляем выражение (g b).

Сохраним это определение в модуле Kleisli, а также определение для функции pred и загрузим модуль

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

из Prelude функцию pred, она также уже определена в Prelude. Для определения нашей функции нам по-

требуется модуль Nat, который мы уже определили. Скопируем файл Nat. hs в ту же директорию, в которой

содержится файл Kleisli. hs и подключим этот модуль. Шапка модуля примет вид:

Примеры специальных функций | 89

a

f

b

b

g

c

Nothing

Nothing

b

a

g

f

c

Nothing

a

f*>g

c

Nothing

Рис. 6.3: Композиция частично определённых функций

module Kleisli where

import Prelude hiding(id, (>> ), pred)

import Nat

Добавим определение экземпляра Kleisli для Maybe в модуль Kleisli а также определение функции

pred. Сохраним обновлённый модуль и загрузим в интерпретатор.

*Kleisli> :load Kleisli

[1 of 2] Compiling Nat

( Nat. hs, interpreted )

[2 of 2] Compiling Kleisli

( Kleisli. hs, interpreted )

Ok, modules loaded: Kleisli, Nat.

*Kleisli> let pred2 = pred *> pred

*Kleisli> let pred3 = pred *> pred *> pred

*Kleisli> let two

= Succ (Succ Zero)

*Kleisli>

*Kleisli> pred two

Just (Succ Zero)

*Kleisli> pred3 two

Nothing

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

стично определённых функций закодировано в функции (*> ) теперь нам не нужно заворачивать значения и

разворачивать их из типа Maybe.

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

лим функцию beside, которая вычисляет соседей для данного числа Пеано.

*Kleisli> let beside = pred +> \a -> (a, a + 2)

*Kleisli> beside Zero