ТЛ; др
{-# LANGUAGE InstanceSigs #-}
newtype Id t = Id t
instance Monad Id where
return :: t -> Id t
return = Id
(=<<) :: (a -> Id b) -> Id a -> Id b
f =<< (Id x) = f x
пролог
Оператор приложения $
функций
forall a b. a -> b
канонически определен
($) :: (a -> b) -> a -> b
f $ x = f x
infixr 0 $
с точки зрения приложения функции Haskell f x
( infixl 10
).
Композиция .
определяется с точки зрения $
как
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x
infixr 9 .
и удовлетворяет эквивалентности forall f g h.
f . id = f :: c -> d Right identity
id . g = g :: b -> c Left identity
(f . g) . h = f . (g . h) :: a -> d Associativity
.
является ассоциативным и id
является его правой и левой идентичностью.
Тройка Клейсли
В программировании монада - это конструктор типа функтора с экземпляром класса типа монады. Существует несколько эквивалентных вариантов определения и реализации, каждый из которых несет в себе несколько разные представления об абстракции монады.
Функтор - это конструктор f
типа * -> *
с экземпляром класса типа функтор.
{-# LANGUAGE KindSignatures #-}
class Functor (f :: * -> *) where
map :: (a -> b) -> (f a -> f b)
В дополнение к следующему статически обязательному протоколу типов экземпляры класса функторов должны подчиняться законам алгебраических функторов. forall f g.
map id = id :: f t -> f t Identity
map f . map g = map (f . g) :: f a -> f c Composition / short cut fusion
Функторные вычисления имеют тип
forall f t. Functor f => f t
Вычисление c r
состоит из результатов r
в контексте c
.
Унарные монадические функции или стрелки Клейсли имеют тип
forall m a b. Functor m => a -> m b
Стрелки Клейси - это функции, которые принимают один аргумент a
и возвращают монадическое вычисление m b
.
Монады канонически определены в терминах тройки Клейсли forall m. Functor m =>
(m, return, (=<<))
реализован как класс типа
class Functor m => Monad m where
return :: t -> m t
(=<<) :: (a -> m b) -> m a -> m b
infixr 1 =<<
Идентичность Клейла return
является Клейл стрелок , которая способствует значению t
в монадическую контексте m
. Приложение Extension или Kleisli =<<
применяет стрелку Kleisli a -> m b
к результатам вычислений m a
.
Состав Клейсли <=<
определяется с точки зрения расширения как
(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x
infixr 1 <=<
<=<
Составляет две стрелки Клейсли, применяя левую стрелку к результатам применения правой стрелки.
Экземпляры класса типа монады должны подчиняться законам монады , наиболее элегантно изложенным в терминах композиции Клейсли:forall f g h.
f <=< return = f :: c -> m d Right identity
return <=< g = g :: b -> m c Left identity
(f <=< g) <=< h = f <=< (g <=< h) :: a -> m d Associativity
<=<
является ассоциативным и return
является его правой и левой идентичностью.
тождественность
Тип личности
type Id t = t
это тождественная функция на типах
Id :: * -> *
Интерпретируется как функтор,
return :: t -> Id t
= id :: t -> t
(=<<) :: (a -> Id b) -> Id a -> Id b
= ($) :: (a -> b) -> a -> b
(<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
= (.) :: (b -> c) -> (a -> b) -> (a -> c)
В каноническом Хаскеле тождественная монада определена
newtype Id t = Id t
instance Functor Id where
map :: (a -> b) -> Id a -> Id b
map f (Id x) = Id (f x)
instance Monad Id where
return :: t -> Id t
return = Id
(=<<) :: (a -> Id b) -> Id a -> Id b
f =<< (Id x) = f x
вариант
Тип опции
data Maybe t = Nothing | Just t
кодирует вычисления, Maybe t
которые не обязательно дают результат t
, вычисления, которые могут «потерпеть неудачу». Опция монада определена
instance Functor Maybe where
map :: (a -> b) -> (Maybe a -> Maybe b)
map f (Just x) = Just (f x)
map _ Nothing = Nothing
instance Monad Maybe where
return :: t -> Maybe t
return = Just
(=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
f =<< (Just x) = f x
_ =<< Nothing = Nothing
a -> Maybe b
применяется к результату, только если Maybe a
дает результат.
newtype Nat = Nat Int
Натуральные числа могут быть закодированы как целые числа, большие или равные нулю.
toNat :: Int -> Maybe Nat
toNat i | i >= 0 = Just (Nat i)
| otherwise = Nothing
Натуральные числа не замкнуты при вычитании.
(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)
infixl 6 -?
Опция monad охватывает базовую форму обработки исключений.
(-? 20) <=< toNat :: Int -> Maybe Nat
Список
Монада списка, над типом списка
data [] t = [] | t : [t]
infixr 5 :
и его аддитивная моноидная операция «добавить»
(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[] ++ ys = ys
infixr 5 ++
кодирует нелинейные вычисления, [t]
дающие естественное количество 0, 1, ...
результатов t
.
instance Functor [] where
map :: (a -> b) -> ([a] -> [b])
map f (x : xs) = f x : map f xs
map _ [] = []
instance Monad [] where
return :: t -> [t]
return = (: [])
(=<<) :: (a -> [b]) -> [a] -> [b]
f =<< (x : xs) = f x ++ (f =<< xs)
_ =<< [] = []
Расширение =<<
объединяет ++
все списки, [b]
полученные в результате применения f x
стрелки Клейсли, a -> [b]
к элементам [a]
в один список результатов [b]
.
Пусть правильные делители натурального числа n
будут
divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]
divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)
тогда
forall n. let { f = f <=< divisors } in f n = []
При определении класса типа монады вместо расширения =<<
стандарт Haskell использует свой flip, оператор связывания>>=
.
class Applicative m => Monad m where
(>>=) :: forall a b. m a -> (a -> m b) -> m b
(>>) :: forall a b. m a -> m b -> m b
m >> k = m >>= \ _ -> k
{-# INLINE (>>) #-}
return :: a -> m a
return = pure
Для простоты в этом объяснении используется иерархия классов типов
class Functor f
class Functor m => Monad m
В Хаскеле текущая стандартная иерархия
class Functor f
class Functor p => Applicative p
class Applicative m => Monad m
потому что не только каждая монада является функтором, но и каждая аппликативная функция является функтором, и каждая монада также является аппликативной.
Используя монаду списка, императивный псевдокод
for a in (1, ..., 10)
for b in (1, ..., 10)
p <- a * b
if even(p)
yield p
примерно переводится в блок do ,
do a <- [1 .. 10]
b <- [1 .. 10]
let p = a * b
guard (even p)
return p
эквивалентное понимание монады ,
[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]
и выражение
[1 .. 10] >>= (\ a ->
[1 .. 10] >>= (\ b ->
let p = a * b in
guard (even p) >> -- [ () | even p ] >>
return p
)
)
Понимание и понимание монады являются синтаксическим сахаром для вложенных выражений связывания. Оператор связывания используется для локального связывания имен монадических результатов.
let x = v in e = (\ x -> e) $ v = v & (\ x -> e)
do { r <- m; c } = (\ r -> c) =<< m = m >>= (\ r -> c)
где
(&) :: a -> (a -> b) -> b
(&) = flip ($)
infixl 0 &
Функция охраны определена
guard :: Additive m => Bool -> m ()
guard True = return ()
guard False = fail
где тип блока или «пустой кортеж»
data () = ()
Аддитивные монады, которые поддерживают выбор и неудачу, могут быть абстрагированы с использованием класса типов
class Monad m => Additive m where
fail :: m t
(<|>) :: m t -> m t -> m t
infixl 3 <|>
instance Additive Maybe where
fail = Nothing
Nothing <|> m = m
m <|> _ = m
instance Additive [] where
fail = []
(<|>) = (++)
где fail
и <|>
образуют моноидforall k l m.
k <|> fail = k
fail <|> l = l
(k <|> l) <|> m = k <|> (l <|> m)
и fail
является поглощающим / уничтожающим нулевым элементом аддитивных монад
_ =<< fail = fail
Если в
guard (even p) >> return p
even p
истинно, тогда сторож производит [()]
, и, по определению >>
, функцию локальной константы
\ _ -> return p
применяется к результату ()
. Если false, то охранник выдает монаду list fail
( []
), которая не дает результата для стрелки Клейсли, которую нужно применить >>
, поэтому p
она пропускается.
государственный
Печально, монады используются для кодирования вычислений с состоянием.
Состояние процессора является функцией
forall st t. st -> (t, st)
который переходит в состояние st
и дает результат t
. Состояние st
может быть что угодно. Ничего, флаг, количество, массив, дескриптор, машина, мир.
Тип государственных процессоров обычно называется
type State st t = st -> (t, st)
Монада процессора состояний - это добрый * -> *
функтор State st
. Клейсли стрелки монады состояния процессора являются функциями
forall st a b. a -> (State st) b
В каноническом Haskell определяется ленивая версия монады процессора состояний
newtype State st t = State { stateProc :: st -> (t, st) }
instance Functor (State st) where
map :: (a -> b) -> ((State st) a -> (State st) b)
map f (State p) = State $ \ s0 -> let (x, s1) = p s0
in (f x, s1)
instance Monad (State st) where
return :: t -> (State st) t
return x = State $ \ s -> (x, s)
(=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
in stateProc (f x) s1
Процессор состояния запускается путем предоставления начального состояния:
run :: State st t -> st -> (t, st)
run = stateProc
eval :: State st t -> st -> t
eval = fst . run
exec :: State st t -> st -> st
exec = snd . run
Доступ к состоянию обеспечивается примитивами get
и put
методами абстрагирования по монадам с состоянием :
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
class Monad m => Stateful m st | m -> st where
get :: m st
put :: st -> m ()
m -> st
объявляет функциональную зависимость типа состояния st
от монады m
; что State t
, например, определит тип состояния, который будет t
уникальным.
instance Stateful (State st) st where
get :: State st st
get = State $ \ s -> (s, s)
put :: st -> State st ()
put s = State $ \ _ -> ((), s)
с типом устройства, используемым аналогично void
в C.
modify :: Stateful m st => (st -> st) -> m ()
modify f = do
s <- get
put (f s)
gets :: Stateful m st => (st -> t) -> m t
gets f = do
s <- get
return (f s)
gets
часто используется с полями доступа к записи.
Монад состояния, эквивалентный переменной threading
let s0 = 34
s1 = (+ 1) s0
n = (* 12) s1
s2 = (+ 7) s1
in (show n, s2)
где s0 :: Int
- одинаково прозрачный, но бесконечно более элегантный и практичный
(flip run) 34
(do
modify (+ 1)
n <- gets (* 12)
modify (+ 7)
return (show n)
)
modify (+ 1)
является вычислением типа State Int ()
, за исключением его эффекта, эквивалентного return ()
.
(flip run) 34
(modify (+ 1) >>
gets (* 12) >>= (\ n ->
modify (+ 7) >>
return (show n)
)
)
Монадический закон ассоциативности можно записать в терминах >>=
forall m f g.
(m >>= f) >>= g = m >>= (\ x -> f x >>= g)
или
do { do { do {
r1 <- do { x <- m; r0 <- m;
r0 <- m; = do { = r1 <- f r0;
f r0 r1 <- f x; g r1
}; g r1 }
g r1 }
} }
Как и в программировании, ориентированном на выражения (например, Rust), последний оператор блока представляет его результат. Оператор связывания иногда называют «программируемой точкой с запятой».
Примитивы структуры управления итерациями из структурированного императивного программирования эмулируются монадически
for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())
while :: Monad m => m Bool -> m t -> m ()
while c m = do
b <- c
if b then m >> while c m
else return ()
forever :: Monad m => m t
forever m = m >> forever m
Ввод, вывод
data World
Монада процессора состояния мира ввода / вывода - это примирение чистого Хаскелла и реального мира функциональной денотативной и императивной операционной семантики. Близкий аналог собственно строгой реализации:
type IO t = World -> (t, World)
Взаимодействие облегчают нечистые примитивы
getChar :: IO Char
putChar :: Char -> IO ()
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
hSetBuffering :: Handle -> BufferMode -> IO ()
hTell :: Handle -> IO Integer
. . . . . .
Примесь кода, который использует IO
примитивы, постоянно протоколируется системой типов. Потому что чистота потрясающая, что происходит внутри IO
, остается внутри IO
.
unsafePerformIO :: IO t -> t
Или, по крайней мере, должен.
Подпись типа программы на Haskell
main :: IO ()
main = putStrLn "Hello, World!"
расширяется до
World -> ((), World)
Функция, которая преобразует мир.
эпилог
Категория, объекты которой относятся к типам Haskell, а морфизмы - это функции между типами Haskell - это «быстрая и свободная» категория Hask
.
Функтор T
- это отображение категории C
в категорию D
; для каждого объекта в C
объекте вD
Tobj : Obj(C) -> Obj(D)
f :: * -> *
и для каждого морфизма в C
морфизме вD
Tmor : HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
map :: (a -> b) -> (f a -> f b)
где X
, Y
объекты в C
. HomC(X, Y)
это класс гомоморфизм всех морфизмов X -> Y
в C
. Функтор должен сохранять индивидуальность и композицию морфизма, «структуру» C
, в D
.
Tmor Tobj
T(id) = id : T(X) -> T(X) Identity
T(f) . T(g) = T(f . g) : T(X) -> T(Z) Composition
Категория Клейла из категории C
даются Клейли тройки
<T, eta, _*>
эндофунктора
T : C -> C
( f
), тождественный морфизм eta
( return
) и оператор расширения *
( =<<
).
У каждого Клейсли морфизм в Hask
f : X -> T(Y)
f :: a -> m b
оператором расширения
(_)* : Hom(X, T(Y)) -> Hom(T(X), T(Y))
(=<<) :: (a -> m b) -> (m a -> m b)
дается морфизм в Hask
категории Клейсли
f* : T(X) -> T(Y)
(f =<<) :: m a -> m b
Композиция в категории Клейсли .T
приведена в терминах расширения
f .T g = f* . g : X -> T(Z)
f <=< g = (f =<<) . g :: a -> m c
и удовлетворяет категории аксиомы
eta .T g = g : Y -> T(Z) Left identity
return <=< g = g :: b -> m c
f .T eta = f : Z -> T(U) Right identity
f <=< return = f :: c -> m d
(f .T g) .T h = f .T (g .T h) : X -> T(U) Associativity
(f <=< g) <=< h = f <=< (g <=< h) :: a -> m d
который, применяя преобразования эквивалентности
eta .T g = g
eta* . g = g By definition of .T
eta* . g = id . g forall f. id . f = f
eta* = id forall f g h. f . h = g . h ==> f = g
(f .T g) .T h = f .T (g .T h)
(f* . g)* . h = f* . (g* . h) By definition of .T
(f* . g)* . h = f* . g* . h . is associative
(f* . g)* = f* . g* forall f g h. f . h = g . h ==> f = g
с точки зрения расширения даны канонически
eta* = id : T(X) -> T(X) Left identity
(return =<<) = id :: m t -> m t
f* . eta = f : Z -> T(U) Right identity
(f =<<) . return = f :: c -> m d
(f* . g)* = f* . g* : T(X) -> T(Z) Associativity
(((f =<<) . g) =<<) = (f =<<) . (g =<<) :: m a -> m c
Монады также могут быть определены в терминах не расширения Клейсляна, а естественного преобразования mu
в вызываемом программировании join
. Монада определяется mu
как тройка над категорией C
эндофунктора
T : C -> C
f :: * -> *
и две природные трансформации
eta : Id -> T
return :: t -> f t
mu : T . T -> T
join :: f (f t) -> f t
удовлетворяя эквивалентности
mu . T(mu) = mu . mu : T . T . T -> T . T Associativity
join . map join = join . join :: f (f (f t)) -> f t
mu . T(eta) = mu . eta = id : T -> T Identity
join . map return = join . return = id :: f t -> f t
Класс типа монады затем определяется
class Functor m => Monad m where
return :: t -> m t
join :: m (m t) -> m t
Каноническая mu
реализация варианта монады:
instance Monad Maybe where
return = Just
join (Just m) = m
join Nothing = Nothing
concat
функция
concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat [] = []
является join
списком монады.
instance Monad [] where
return :: t -> [t]
return = (: [])
(=<<) :: (a -> [b]) -> ([a] -> [b])
(f =<<) = concat . map f
Реализации join
могут быть переведены из формы расширения с использованием эквивалентности
mu = id* : T . T -> T
join = (id =<<) :: m (m t) -> m t
Обратный перевод из mu
формы расширения в форму
f* = mu . T(f) : T(X) -> T(Y)
(f =<<) = join . map f :: m a -> m b
Но почему такая абстрактная теория должна быть полезна для программирования?
Ответ прост: как компьютерщики, мы ценим абстракцию ! Когда мы разрабатываем интерфейс к программному компоненту, мы хотим , чтобы он как можно меньше раскрывал реализацию. Мы хотим иметь возможность заменить реализацию многими альтернативами, многими другими «экземплярами» той же «концепции». Когда мы разрабатываем общий интерфейс для многих программных библиотек, еще более важно, чтобы у выбранного нами интерфейса было множество реализаций. Это общность концепции монады, которую мы так высоко ценим, потому что теория категорий настолько абстрактна, что ее концепции так полезны для программирования.
Поэтому неудивительно, что обобщение монад, которое мы представляем ниже, также имеет тесную связь с теорией категорий. Но мы подчеркиваем, что наша цель очень практична: это не «реализовать теорию категорий», а найти более общий способ структурирования библиотек комбинаторов. Нам просто повезло, что математики уже проделали большую часть работы для нас!
от обобщающих монад до стрел Джона Хьюза