Haskell Language
Монады
Поиск…
Вступление
Монада - это тип данных составных действий. Monad
- это класс конструкторов типов, значения которых представляют такие действия. Возможно , IO
является самым узнаваемым один: значение IO a
является «рецептом для получения a
значении от реального мира».
Мы говорим, что конструктор типа m
(такой как []
или Maybe
) образует монаду, если есть instance Monad m
удовлетворяющий некоторым законам о составе действий. Тогда мы можем рассуждать о ma
как «действие, результат которого имеет тип a
».
Возможно, монада
Maybe
, используется для представления возможных пустых значений - аналогично null
на других языках. Обычно он используется как выходной тип функций, которые могут каким-то образом сбой.
Рассмотрим следующую функцию:
halve :: Int -> Maybe Int
halve x
| even x = Just (x `div` 2)
| odd x = Nothing
Подумайте о halve
как действия, в зависимости от Int
, который пытается вдвое уменьшить целое число, если нечетно.
Как мы halve
число целых чисел?
takeOneEighth :: Int -> Maybe Int -- (after you read the 'do' sub-section:)
takeOneEighth x =
case halve x of -- do {
Nothing -> Nothing
Just oneHalf -> -- oneHalf <- halve x
case halve oneHalf of
Nothing -> Nothing
Just oneQuarter -> -- oneQuarter <- halve oneHalf
case halve oneQuarter of
Nothing -> Nothing -- oneEighth <- halve oneQuarter
Just oneEighth ->
Just oneEighth -- return oneEighth }
-
takeOneEighth
- это последовательность из трехhalve
шагов, соединенных вместе. - Если шаг на
halve
не удался, мы хотим, чтобы вся композицияtakeOneEighth
потерпела неудачу. - Если шаг с
halve
успеха завершен, мы хотим передать его результат вперед.
instance Monad Maybe where
-- (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>= f = Nothing -- infixl 1 >>=
Just x >>= f = Just (f x) -- also, f =<< m = m >>= f
-- return :: a -> Maybe a
return x = Just x
и теперь мы можем написать:
takeOneEighth :: Int -> Maybe Int
takeOneEighth x = halve x >>= halve >>= halve -- or,
-- return x >>= halve >>= halve >>= halve -- which is parsed as
-- (((return x) >>= halve) >>= halve) >>= halve -- which can also be written as
-- (halve =<<) . (halve =<<) . (halve =<<) $ return x -- or, equivalently, as
-- halve <=< halve <=< halve $ x
Состав Клейсли <=<
определяется как (g <=< f) x = g =<< fx
или эквивалентно как (f >=> g) x = fx >>= g
. При этом указанное выше определение становится
takeOneEighth :: Int -> Maybe Int
takeOneEighth = halve <=< halve <=< halve -- infixr 1 <=<
-- or, equivalently,
-- halve >=> halve >=> halve -- infixr 1 >=>
Существует три закона монады, которые должны соблюдаться каждой монадой, то есть каждый тип, который является экземпляром класса Monad
:
1. return x >>= f = f x
2. m >>= return = m
3. (m >>= g) >>= h = m >>= (\y -> g y >>= h)
где m
- монада, f
имеет тип a -> mb
и g
имеет тип b -> mc
.
Или, что то же самое, используя оператор состава >=>
Клейсли, определенный выше:
1. return >=> g = g -- do { y <- return x ; g y } == g x
2. f >=> return = f -- do { y <- f x ; return y } == f x
3. (f >=> g) >=> h = f >=> (g >=> h) -- do { z <- do { y <- f x; g y } ; h z }
-- == do { y <- f x ; do { z <- g y; h z } }
Соблюдение этих законов значительно облегчает рассуждение о монаде, потому что оно гарантирует, что использование монадических функций и составление их ведет себя разумно, подобно другим монадам.
Давайте проверим, Maybe
монада Maybe
подчиняться трем законам монады.
- Закон левого тождества -
return x >>= f = fx
return z >>= f
= (Just z) >>= f
= f z
- Правильный закон тождества -
m >>= return = m
-
Just
конструктор данных
Just z >>= return
= return z
= Just z
-
Nothing
конструктора данных
Nothing >>= return
= Nothing
- Закон ассоциативности -
(m >>= f) >>= g = m >>= (\x -> fx >>= g)
-
Just
конструктор данных
-- Left-hand side
((Just z) >>= f) >>= g
= f z >>= g
-- Right-hand side
(Just z) >>= (\x -> f x >>= g)
(\x -> f x >>= g) z
= f z >>= g
-
Nothing
конструктора данных
-- Left-hand side
(Nothing >>= f) >>= g
= Nothing >>= g
= Nothing
-- Right-hand side
Nothing >>= (\x -> f x >>= g)
= Nothing
IO monad
Невозможно получить значение типа a
из выражения типа IO a
и его не должно быть. На самом деле это большая часть того, почему монады используются для моделирования IO
.
Выражение типа IO a
можно представить как представляющее действие, которое может взаимодействовать с реальным миром и, если оно выполняется, приведет к чему-то типа a
. Например, функция getLine :: IO String
из прелюдии не означает, что под getLine
есть определенная строка, которую я могу извлечь - это означает, что getLine
представляет действие получения строки со стандартного ввода.
Неудивительно, что main :: IO ()
поскольку программа Haskell представляет собой вычисление / действие, которое взаимодействует с реальным миром.
То, что вы можете сделать с выражениями типа IO a
потому что IO
является монадой:
Последовательность двух действий с использованием
(>>)
для создания нового действия, выполняющего первое действие, отбрасывает любое значение, которое оно произвело, а затем выполняет второе действие.-- print the lines "Hello" then "World" to stdout putStrLn "Hello" >> putStrLn "World"
Иногда вы не хотите отбрасывать значение, которое было создано в первом действии, - вам действительно хотелось бы, чтобы его подавали во второе действие. Для этого имеем
>>=
. ДляIO
он имеет тип(>>=) :: IO a -> (a -> IO b) -> IO b
.-- get a line from stdin and print it back out getLine >>= putStrLn
Возьмите нормальное значение и преобразуйте его в действие, которое сразу же возвращает значение, которое вы ему дали. Эта функция менее очевидно , полезно , пока вы не начнете использовать
do
запись.-- make an action that just returns 5 return 5
Больше из Haskell Wiki на монаде IO здесь .
Список Монад
Списки образуют монаду. У них есть экземпляр монады, эквивалентный этому:
instance Monad [] where return x = [x] xs >>= f = concat (map f xs)
Мы можем использовать их для подражания недетерминированности в наших вычислениях. Когда мы используем xs >>= f
, функция f :: a -> [b]
отображается в списке xs
, получая список списков результатов каждого применения f
по каждому элементу xs
и всех списков результаты затем объединяются в один список всех результатов. В качестве примера мы вычисляем сумму двух недетерминированных чисел, используя do-notation , причем сумма представлена списком сумм всех пар целых чисел из двух списков, каждый из которых представляет все возможные значения недетерминированного числа:
sumnd xs ys = do
x <- xs
y <- ys
return (x + y)
Или, что то же самое, используя liftM2
в Control.Monad
:
sumnd = liftM2 (+)
мы получаем:
> sumnd [1,2,3] [0,10]
[1,11,2,12,3,13]
Монад как подкласс прикладной
Начиная с GHC 7.10, Applicative
является суперклассом Monad
(т. Monad
Каждый тип, который является Monad
также должен быть Applicative
). Все методы Applicative
( pure
, <*>
) могут быть реализованы с точки зрения методов Monad
( return
, >>=
).
Очевидно, что pure
и return
служат эквивалентным целям, поэтому pure = return
. Определение для <*>
слишком относительно ясно:
mf <*> mx = do { f <- mf; x <- mx; return (f x) }
-- = mf >>= (\f -> mx >>= (\x -> return (f x)))
-- = [r | f <- mf, x <- mx, r <- return (f x)] -- with MonadComprehensions
-- = [f x | f <- mf, x <- mx]
Эта функция определяется как ap
в стандартных библиотеках.
Таким образом, если вы уже определили экземпляр Monad
для типа, вы можете получить экземпляр Applicative
для него «бесплатно», указав
instance Applicative < type > where
pure = return
(<*>) = ap
Как и в случае с монадскими законами, эти эквивалентности не применяются, но разработчики должны следить за тем, чтобы они всегда поддерживались.
Нет общего способа извлечь значение из монадического вычисления
Вы можете переносить значения в действия и обрабатывать результат одного вычисления в другом:
return :: Monad m => a -> m a
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Однако определение Монады не гарантирует существования функции типа Monad m => ma -> a
.
Это означает, что вообще нет способа извлечь значение из вычисления (т. Е. «Развернуть» его). Это касается многих случаев:
extract :: Maybe a -> a
extract (Just x) = x -- Sure, this works, but...
extract Nothing = undefined -- We can’t extract a value from failure.
В частности, нет функции IO a -> a
, которая часто путает новичков; см. этот пример .
делать-нотация
do
-notation - синтаксический сахар для монад. Вот правила:
do x <- mx do x <- mx y <- my is equivalent to do y <- my ... ...
do let a = b let a = b in ... is equivalent to do ...
do m m >> ( e is equivalent to e)
do x <- m m >>= (\x -> e is equivalent to e)
do m is equivalent to m
Например, эти определения эквивалентны:
example :: IO Integer
example =
putStrLn "What's your name?" >> (
getLine >>= (\name ->
putStrLn ("Hello, " ++ name ++ ".") >> (
putStrLn "What should we return?" >> (
getLine >>= (\line ->
let n = (read line :: Integer) in
return (n + n))))))
example :: IO Integer
example = do
putStrLn "What's your name?"
name <- getLine
putStrLn ("Hello, " ++ name ++ ".")
putStrLn "What should we return?"
line <- getLine
let n = (read line :: Integer)
return (n + n)
Определение Монады
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
Наиболее важной функцией для работы с монадами является оператор связывания >>=
:
(>>=) :: m a -> (a -> m b) -> m b
- Подумайте о
ma
, как «действия сa
результате». - Подумайте
a -> mb
«действия ( в зависимости от какa
параметре) сb
. Результат».
>>=
последовательно выполняет два действия, связывая результат от первого действия ко второму.
Другая функция, определенная Monad
:
return :: a -> m a
Его имя несчастливо: это return
имеет ничего общего с ключевым словом return
найденным на императивных языках программирования.
return x
- это тривиальное действие, приводящее к результату x
. (Это тривиально в следующем смысле :)
return x >>= f ≡ f x -- “left identity” monad law
x >>= return ≡ x -- “right identity” monad law