значение, то оно подставляется в следующую частично определённую функцию. Если же первая функция не
смогла вычислить результат и вернула Nothing
, то считается что вся функция (f*> g) вернула Nothing.Теперь давайте закодируем это определение в Haskell. При этом мы воспользуемся нашим классом
Kleisli
. Аналогом функции id для частично определённых функций будет функция, которая просто за-ворачивает значение в конструктор Just
.instance Kleisli Maybe where
idK
= Just
f *>
g = \a -> case f a ofNothing -> 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 twoJust
(Succ Zero)*Kleisli>
pred3 twoNothing
Обратите внимание на то, как легко определяются производные функции. Желаемое поведение для ча-
стично определённых функций закодировано в функции (*>
) теперь нам не нужно заворачивать значения иразворачивать их из типа Maybe
.Приведём пример функции, которая составлена из частично определённой функции и обычной. Опреде-
лим функцию beside, которая вычисляет соседей для данного числа Пеано.
*Kleisli> let
beside = pred +> \a -> (a, a + 2)*Kleisli>
beside ZeroNothing
*Kleisli>
beside twoJust
(Succ Zero, Succ (Succ (Succ Zero)))*Kleisli>
(pred *> beside) twoJust
(Zero, Succ (Succ Zero))В выражении
pred +>
\a -> (a, a + 2)Мы сначала вычисляем предыдущее число, и если оно есть составляем пару из \a ->
(a, a+2), в парупопадёт данное число и число, следующее за ним через одно. Поскольку сначала мы вычислили предыдущее
число в итоговом кортеже окажется предыдущее число и следующее.
90 | Глава 6: Функторы и монады: теория
Итак с помощью функций из класса Kleisli
мы можем составлять частично определённые функции вбесточечном стиле. Обратите внимание на то, что все функции кроме pred были составлены в интерпрета-
торе.Отметим, что в Prelude
определена специальная функция maybe, которая похожа на функцию foldr длясписков, она заменяет в значении типа Maybe
конструкторы на функции. Посмотрим на её определение:maybe
::
b -> (a -> b) -> Maybe a -> bmaybe n f Nothing
=
n
maybe n f (Just
x) =f x
С помощью этой функции мы можем переписать определение экземпляра Kleisli
так:instance Kleisli Maybe where
idM
= Just
f *>
g=
f >> maybe Nothing gМногозначные функции
Многозначные функции ветрены и непостоянны. Для некоторых значений аргументов они возвращают
одно значение, для иных десять, а для третьих и вовсе ничего. В Haskell такие функции имеют тип a ->
[b].Функция возвращает список ответов. На (рис. 6.4) изображена схема многозначной функции.
a
f
b
Рис. 6.4: Многозначная функция
Приведём пример. Системы Линденмайера (или L-системы) моделируют развитие живого организма.
Считается, что организм состоит из последовательности букв (или клеток). В каждый момент времени одна
буква заменяется на новую последовательность букв, согласно определённым правилам. Так организм живёт
и развивается. Приведём пример:
У нас есть два правила размножения клеток-букв в организме. На каждом этапе мы во всём слове заме-
няем букву
сложному слову.
Опишем этот процесс в Haskell. Для этого определим правила развития организма в виде многозначной
функции: