Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Функциональное программирование. Конспект лекций

.pdf
Скачиваний:
0
Добавлен:
15.08.2026
Размер:
1 Мб
Скачать

(const a +> f) () :: mb.

Рассмотрим функцию применения «обернутого» значения к «обернутой» функции:

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

и ее реализацию

mf $$ mx = ( +$ mx) *$ mf, где mf:: m (a -> b); mx:: m a;

( +$ mx)::(a -> b) -> m b; (*$)::(a’ -> m b) -> m a’ -> m b.

Полагая a’ = (a -> b), запишем

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

Тогда

( +$ mx) *$ mf :: m b.

Таким образом, имея «обернутую» функцию и «обернутое» значение, можно применить данное значение к функции и получить «обернутый» результат с помощью ($$). Все это реализовано за счет функций (*$) и (+$).

Применение функций многих переменных имеет некоторую особенность. Рассмотрим функцию применения к обычной функции монадического значения:

lift1 :: (a -> b) -> m a -> m b.

Нетрудно увидеть, что это (+$) :: (a -> b) -> m a -> m b. Говорят, что функция lift1 «поднимает» обычную функцию (a -> b) в «монадический» мир m a -> m b.

Поскольку функция двух аргументов может рассматриваться как функция одного аргумента, которая возвращает функцию, то можно применить первый аргумент ma с помощью функции 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.

– 81 –

Осталось применить второй аргумент m b к функции типа m (b -> c), для чего используется функция ($$). Таким образом для функции двух аргументов получим:

lift2 :: (a -> b -> c) -> m a -> m b -> m c lift2 f a b = lift1 f a $$ b.

Для функции трех аргументов:

lift3 :: Kleisli m => (a -> b -> c -> d) -> m a -> m b -> m c -> m d lift3 f a b c = lift2 f a b $$ c.

Так можно определить любую функцию liftN через функции liftN-1 и $$.

8.3. Функторы и монады

Определение класса Functor: class Functor f where fmap :: (a -> b) -> f a -> f b.

Тип метода fmap совпадает с типом для функции +$ класса Клейсли:

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

Необходимо заменить m на f и зависимость от Kleisli на зависимость от Functor.

Итак, в Haskell есть базовая операция fmap применения обычной функции к значению из мира специальных функций. В модуле Control.Applicative определен инфиксный синоним <$> для этой функции.

Аппликативные функторы

class Functor f => Applicative f where pure :: a -> f a

(<*>) :: f (a -> b) -> f a -> f b

idK == pure и $$ == (<*>) . Если для данного типа f определен экземпляр класса Applicative, то из контекста следует, что для него также определен и экземпляр класса Functor. С помощью функций

– 82 –

<*> (или $$) можно составить функции liftN, которые поднимают обычные функции произвольного числа аргументов в мир специальных значений.

Класс Applicative определен в модуле Control.Applicative, там же можно найти и функции liftA, liftA2, liftA3, и символьный синоним <$> для функции fmap. Функции liftAn определены так:

liftA2 f a b = f <$> a <*> b

liftA3 f a b c = f <$> a <*> b <*> c.

Эти определения с точностью до обозначений совпадают с теми, что для класса Kleisli.

Монады:

class Monad m where return :: a -> m a

(>>=) :: m a -> (a -> m b) -> m b.

Функция return :: a -> m a == idK. Если «перевернуть» функцию (>>=) с помощью flip:

(=<<) = flip (>>=),

то, поменяв аргументы местами, получим функцию (*$). Итак, функция связывания – это функция применения специальной функции к специальному значению.

В модуле Prelude определены экземпляры класса Monad для ти-

пов Maybe и [].

8.4. Mонада State

Монада State предназначена для вычисления функций с «состоянием», когда результат вычисления функции зависит от параметра состояния, т.е. для функций вида f::a->s->(a,s). Для учета состояния предлагается использовать «специальные» функции, которые принимают аргумент обычного типа a и возвращают «специальное» значение:

State (s->(a,s)).

Для этого вводится тип данных State с двумя параметрами (типами) s a:

data State s a = State (s -> (a, s)).

– 83 –

Для создания такого типа используется конструктор State, которому передается функция f::(s -> (a, s)). Для извлечения функции f, в которую «заворачивается» тип a, используется функция:

runState :: State s a -> s -> (a, s) runState (State f) = f.

Для работы с функциями, которые оперируют с типом State s a, используют специальные операции.

Проблема заключается в том, что если специальная функция возвращает «обернутое» значение m a, но далее требуется применить тип a к другой функции. Надо «убрать обертку» m, чтобы использовать a. Реализация класса Kleisli для типа State s выглядит следующим образом:

instance Kleisli (State s) where idK x = State $ \s -> (x, s) f *> g = \x -> State $ \s0 ->

let (y, s1) = runState (f x) s0 in runState (g y) s1.

В реализации idK конструктор State применяется к простейшей лямбда-функции, которая «упаковывает» аргумент a в функцию, т.е. упаковывает тип a в тип State s a. Ключевым явялется определение функции композиции f *> g. Опять используется лямбдафункция, в которой конструктор State применяется к другой лямб- да-функции. После применения (f х) возвращается «монадический» тип State s b (так как f::(a -> m b)). Для извлечения b из m b применяется функция runState (f х) и возвращается функция типа s -> (a, s). После применения аргумента лямбда-функции s0 будет получена пара (y, s1). Важно отметить, что получение пары (y, s1) возможно только после задания начального состояния s0.

Далее g применяется к y и возвращается «монадическое» знача-

ние типа State s с (g:: (b -> m c)). Применение runState (g y) извле-

кает функцию типа s -> (с, s) из m c, к которой применяется s1. В результате возвращается пара (с, s1). Таким образом функция f *> g :: (a -> m c) имеет тип State (s -> (c, s)).

Очень важным моментом является передача состояния от применения f к применению g. «Распаковка» «монадического» состояния происходит с помощью runState. Для инициализации вычисле-

– 84 –

ний необходимо задать начальное значение состояния s0, все последующие состояния вычисляются из предыдущего.

Определив функцию композиции (*>) и idK, можно все остальные функции (+>), (*$), (+$), ($$) получить из них:

f +> g = f *> (g >> idK).

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

f +> g = \a -> State $ \s0 ->

let (b, s1) = runState (f a) s0 in runState ((g >> idK) b) s1.

Заменяя ((g >> idK) b), будем иметь f +> g = \a -> State $ \s0 ->

let (b, s1) = runState (f a) s0

in runState (State $ \s -> (g b, s)) s1 f +> g = \a -> State $ \s0 ->

let (b, s1) = runState (f a) s0 in (g b, s1))

f (*$) mx = ((const mx) *> f ) ().

Учитывая, что f:: (a -> m b), mx :: m a, const :: m a -> b -> m a,

получим

(const mx) :: b->mx

((const mx) *> f) () = [\a -> State $ \s0 ->

let (b, s1) = runState ((const mx) a) s0 in runState (f b) s1] ().

Применяя значение единичного типа () к лямбда-функции в квадратных скобках, получим

((const x) *> f) () = State $ \s0 ->

let (b, s1) = runState mx s0 in runState (f b) s1.

Таким образом,

f (*$) mx = State $ \s0 ->

– 85 –

let (b, s1) = runState mx s0 in runState (f b) s1

f (+$) mx = ((const mx) +> f) ().

Здесь f:: (a -> b) – обычная функция, x :: m a, тогда

((const mx) +> f) () = [\a -> State $ \s0 ->

let (x, s1) = runState ((const mx) a) s0 in (f x, s1))] ().

Применяя значение единичного типа () к лямбда-функции в квадратных скобках, получим

((const mx) +> f) () = State $ \s0 ->

let (x, s1) = runState mx s0 in (f x, s1))].

Таким образом,

f (+$) mx = State $ \s0 ->

let (x, s1) = runState mx s0 in (f x, s1)).

Теперь найдем выражение для функции ($$) :: m (a -> b) -> m a -> m b:

mf $$ mx = ( +$ mx) *$ mf.

Здесь mf:: m (a -> b) – «обернутая» функция, mx :: m a. Рассмотрим по отдельности правую и левую часть выражения ( +$ mx) *$ mf.

Выражение (+$) mx представляет собой частичное применение функции g (+$) mx. Учитывая, что mx=State $ \x->(x,s), можно записать:

( +$ mx) = g (+$) = State $ \s0 ->

let (b, s1) = runState mx s0 in (g b, s1))

( +$ mx) = g (+$) = State $ \s0 ->

let (x, s0) = ((\x->(x,s)) s0 in (g x, s0)).

– 86 –

Учитывая полученное ранее выражение для h (*$) mx = State $ \s0 ->

let (b, s1) = runState mx s0 in runState (h b) s1,

получим

( +$ mx) (*$) mf = State $ \s0 ->

let (f, s1) = runState mf s0 in runState (( +$ mx) f) s1

mf $$ mx = State $ \s0 ->

let (f, s1) = runState mf s0 in runState (( +$ mx) f) s1.

Можно сказать, что в выражении let (f, s1) = runState mf s0 происходит «разворачивание» (очистка) «монадической» функции mf, а в runState (( +$ mx) f) s1 – применение очищенной функции f к очищенному значению x. Конструктор State «заворачивает» все в монаду State.

И в заключение проиллюстрируем операцию сложения двух монадических значений State a s. Для этого используется функция

lift2 :: (a -> b -> c) -> m a -> m b -> m c,

реализацию которой можно записать как lift2 f mx my = (+$) f mx $$ my,

где f :: (a -> b -> c) = (+) – операция сложения; mx :: m a

my :: m b.

Рассмотрим сначала выражение (+$) f mx == ((+) (+$) mx). В соответствии с полученным ранее выражением для (+$), запишем:

((+) (+$) mx) = State $ \s0 ->

let (x, s1) = runState mx s0 in ((+) x, s1)).

Теперь рассмотрим операцию применения ((+) (+$) mx) $$ my. Данное выражение можно записать как

– 87 –

( +$ my) (*$) ((+) (+$) mx).

Составляющие данного выражения можно представить как

( +$ my) :: (a->b) -> State s b ( +$ my) = \f -> State $ \s0 ->

let (y, s1) = runState my s0 in (f y, s1))

( +$ my) (*$) ((+) (+$) mx) = State $ \s0 ->

let (f, s1) = runState ((+) (+$) mx) s0 in runState (( +$ my) f) s1

Таким образом, для сложения двух «монадических» значений происходит извлечение этих значений, сложение и «упаковка» результата в монады.

8.4.1. Пример использования монады State

Рассмотрим программу формирования и сложения двух случайных чисел с помощью монады State:

module Kleisli where

import Prelude hiding (id, (>>), pred, (*>), (+>), sequence)

class Kleisli m where idK :: a -> m a

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

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

f +> g = f *> (g >> idK)

data State s a = State (s -> (a, s)) runState :: State s a -> s -> (a, s) runState (State f) = f

instance Kleisli (State s) where

– 88 –

idK a = State $ \s -> (a, s) f *> g = \a -> State $ \s0 ->

let (b, s1) = runState (f a) s0 in runState (g b) s1

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

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

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

lift1 :: Kleisli m => (a -> b) -> m a -> m b lift1 f a = (const a +> f) ()

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

lift2 f a b = lift1 f a $$ b

nextRandom :: Double -> Double

nextRandom = snd . properFraction . (105.947 * )

type Random a = State Double a next :: Random Double

next = State $ \s -> (s, nextRandom s)

mynext::Int -> Random Double mynext = \_ -> next

myaddRandom2 :: Random Double myaddRandom2 = lift2 (+) next1 next2

В программе псевдослучайные числа формируются с помощью функции nextRandom, причем для формирования очередного случайного числа используется предыдущее. Это реализовано с помощью монады State:

– 89 –

next :: Random Double

next = State $ \s -> (s, nextRandom s).

Напомним, что монада State определена как data State s a = State (s -> (a, s)), т.е. в next в качестве функции задана лямбда-функция, в которой в элементе кортежа a хранится s, а в переменной состояния

– nextRandom s. В этом случае при последовательном формировании случайных чисел очередное случайное число будет вычисляться рекурсивно как

nextRandom (nextRandom s).

Для получения первого случайного числа надо задать начальное значение состояния. Посмотреть на случайное число просто так нельзя, так как оно «завернуто» внутри монады. Для извлечения надо воспользоваться функцией runState, т.е. выполнить код:

*Kleisli> runState next1 1 (1.0,0.9470000000000027)

Здесь next1 = State $ \s -> (s, nextRandom s). Применение next1 к runState возвращает лямбда-функцию \s -> (s, nextRandom s). Если теперь применить 1 к лямбда-функции, то получим кортеж (1, nextRandom 1) = (1, snd . properFraction . (105.947 * 1)). Второе случайное число будет вычисляться как nextRandom 0.9470000000000027 = 0.3318090000002911 и т.д. Сложение двух случайных чисел выполняется функцией myaddRandom2. Для получения результата сложения надо воспользоваться функцией runState и задать начальное состояние монады State:

*Kleisli> runState myaddRandom2 1 (1.9470000000000027,0.3318090000002911)

Результатом сложения будет кортеж, в котором в первом элементе находится сумма начального значения (1) и первого случайного числа (0.9470000000000027), а во втором – второе случайное число 0.3318090000002911.

Данный пример иллюстрирует использование монады State для генерации последовательности случайных чисел. Очередное случайное число использует значение предыдущего случайного числа,

– 90 –

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]