haskell-notes

из аргументов частично определённой функции не определён, то не определено всё значение. Сравните с

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

По той же причине в последнем выражении мы получили три копии первого списка. Так произошло

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

x, где x пробегает по элементам списка слева от (<*).

Аналогичный метод есть и в классе Monad:

class

Monad m

where

return

:: a -> m a

(>>=)

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

(>> )

:: m a -> m b -> m b

fail

:: String -> m a

m >> k

= m >>= const k

fail s

= error s

Функция >> в классе Monad, которую мы прятали из-за символа композиции, является аналогом постоян-

ной функции в классе Monad. Она работает так же как и *> . Функция fail используется для служебных нужд

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

Monad достаточно определить методы return и >>=.

Исторические замечания

Напрашивается вопрос. Зачем нам функции return и pure или *> и >> ? Если вы заглянете в документа-

цию к модулю Control.Monad, то там вы найдёте функции liftM, liftM2, liftM3, которые выполняют те же

операции, что и аналогичные функции из модуля Control.Applicative.

Стандартные библиотеки устроены так, потому что класс Applicative появился гораздо позже класса

Monad. И к появлению этого нового класса уже накопилось огромное число библиотек, которые рассчитаны

на прежние имена. Но в будущем возможно прежние классы будут заменены на такие классы:

class Functor f where

fmap :: (a -> b) -> f a -> f b

class Pointed f where

pure :: a -> f a

class (Functor f, Pointed f) => Applicative f where

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

(*> )

:: f a -> f b -> f b

(<*)

:: f a -> f b -> f a

class Applicative f => Monad f where

(>>=) :: f a -> (a -> f b) -> f b

Функторы и монады | 99

6.5 Краткое содержание

В этой главе мы долгой обходной дорогой шли к понятию монады и функтора. Эти классы служат для

облегчения работы в мире специальных функций вида a -> m b, в категории Клейсли

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

мента:

class Functor f where

fmap :: (a -> b) -> f a -> f b

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

числа аргументов:

class Functor f => Applicative f where

pure

:: a -> f a

 

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

liftA

:: Applicative f => (a -> b) -> f a -> f b

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c

liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d

С помощью класса Monad можно применять специальные значения к специальным функциям.

class Monad m where

return

:: a -> m a

(>>=)

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

Функция return является функцией id в мире специальных функций, а функция >>= является функцией

применения ($), с обратным порядком следования аргументов. Вспомним также класс Kleisli, на примере

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

class Kleisli m where

idK

:: a -> m a

(*> )

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

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

Частично определённые функции

a -> Maybe b

data Maybe a = Nothing | Just a

Многозначные функции

a -> [b]

data [a] = [] | a : [a]

6.6 Упражнения

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

Kleisli и Monad.

Функции с состоянием

b

a

f

s

s

Рис. 6.6: Функция с состоянием

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

В Haskell нельзя изменять значения. Новые сложные значения описываются в терминах базовых значе-

ний. Но как же тогда мы сможем описать функцию с состоянием? Функцию, которая принимает на вход

значение, составляет результат на основе внутреннего состояния и значения аргумента и обновляет состоя-

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

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

такой тип:

a -> s -> (b, s)

Функция принимает одно значение типа a и состояние типа s, а возвращает пару, которая состоит из

результата типа b и обновлённого состояния. Если мы введём синоним:

type State s b = s -> (b, s)

И вспомним о частичном применении, то мы сможем записать тип функции с состоянием так:

a -> State s b

В Haskell пошли дальше и выделили для таких функций специальный тип:

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

runState :: State s a -> s -> (a, s)

runState (State f) = f

b

c

a

f

b

g

s

s

s

s

b

c

a

g

f

s

s

s

c

a

f*>g

s

s

Рис. 6.7: Композиция функций с состоянием

Функция runState просто извлекает функцию из оболочки State.

На (рис. 6.6) изображена схема функции с состоянием. В сравнении с обычной функцией у такой функции

один дополнительный выход и один дополнительный вход типа s. По ним течёт и изменяется состояние.

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

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