Монада - это коробка со специальной машиной, которая позволяет вам сделать одну обычную коробку из двух вложенных коробок, но при этом сохраняя некоторую форму обеих коробок.
Конкретно, он позволяет вам выполнять join
типа Monad m => m (m a) -> m a
.
Также необходимо действие return
, которое просто оборачивает значение. return :: Monad m => a -> m a
Вы также можете сказать join
распаковки и return
обертывания - но join
это , а не типа Monad m => m a -> a
(он не разворачивает все монады, он разворачивает монады с монадами внутри них.)
Таким образом, он берет коробку Монады (Monad m =>
, m
) с коробкой внутри нее ((m a)
) и создает нормальную коробку (m a
).
Однако, как правило, монада используется в терминах оператора (>>=)
(разговорный «связывание»), который, по сути, просто fmap
и join
друг за другом. В частности,
x >>= f = join (fmap f x)
(>>=) :: Monad m => (a -> m b) -> m a -> m b
Обратите внимание, что функция приходит во втором аргументе, в отличие от fmap
.
Также join = (>>= id)
.
Теперь, почему это полезно? По сути, он позволяет создавать программы, объединяющие действия вместе, работая в какой-то среде (Monad).
Самым заметным применением Монад в Хаскеле является IO
Монада.
Теперь IO
- это тип, который классифицирует Action в Haskell. Здесь система Монады была единственным способом сохранения (большие красивые слова):
- Ссылочная прозрачность
- ленивость
По сути, действие ввода-вывода, такое как getLine :: IO String
, не может быть заменено строкой, так как оно всегда имеет другой тип. Думайте о IO
как о волшебной шкатулке, которая телепортирует вещи к вам.
Тем не менее, просто сказав, что getLine :: IO String
и все функции принимают IO a
, возникает хаос, поскольку, возможно, функции не понадобятся Что бы const "üp§" getLine
сделал? (const
отбрасывает второй аргумент. const a b = a
.) getLine
не нужно оценивать, но он должен выполнять IO! Это делает поведение довольно непредсказуемым, а также делает систему типов менее «чистой», поскольку все функции будут принимать значения a
и IO a
.
Введите IO
Монаду.
Чтобы связать действия вместе, вы просто выравниваете вложенные действия.
И чтобы применить функцию к выводу действия ввода-вывода, a
в типе IO a
, вы просто используете (>>=)
.
Например, для вывода введенной строки (для вывода строки используется функция, которая производит IO-действие, соответствующее правому аргументу >>=
):
getLine >>= putStrLn :: IO ()
-- putStrLn :: String -> IO ()
Это может быть написано более интуитивно с окружением do
:
do line <- getLine
putStrLn line
По сути, блок do
выглядит так:
do x <- a
y <- b
z <- f x y
w <- g z
h x
k <- h z
l k w
... превращается в это:
a >>= \x ->
b >>= \y ->
f x y >>= \z ->
g z >>= \w ->
h x >>= \_ ->
h z >>= \k ->
l k w
Существует также оператор >>
для m >>= \_ -> f
(когда значение в блоке не требуется для создания нового блока в блоке)
Также можно написать a >> b = a >>= const b
(const a b = a
)
Кроме того, оператор return
моделируется после IO-intuituion - он возвращает значение с минимальным контекстом , в данном случае без IO . Поскольку a
в IO a
представляет возвращаемый тип, это похоже на что-то вроде return(a)
в императивных языках программирования - но оно не останавливает цепочку действий! f >>= return >>= g
равно то же , что и f >>= g
. Это полезно только в том случае, если возвращаемый вами термин был создан ранее в цепочке - см. Выше.
Конечно, есть и другие монады, в противном случае это не будет называться монадой, это будет называться чем-то вроде "IO Control".
Например, монада списка (Monad []
) сглаживается путем конкатенации - оператор (>>=)
выполняет функцию для всех элементов списка. Это можно рассматривать как «индетерминизм», где List - это множество возможных значений, а Monad Framework создает все возможные комбинации.
Например (в GHCi):
Prelude> [1, 2, 3] >>= replicate 3 -- Simple binding
[1, 1, 1, 2, 2, 2, 3, 3, 3]
Prelude> concat (map (replicate 3) [1, 2, 3]) -- Same operation, more explicit
[1, 1, 1, 2, 2, 2, 3, 3, 3]
Prelude> [1, 2, 3] >> "uq"
"uququq"
Prelude> return 2 :: [Int]
[2]
Prelude> join [[1, 2], [3, 4]]
[1, 2, 3, 4]
, потому что:
join a = concat a
a >>= f = join (fmap f a)
return a = [a] -- or "= (:[])"
Монада Maybe просто аннулирует все результаты до Nothing
, если это когда-либо произойдет.
То есть привязка автоматически проверяет, возвращает ли функция (a >>=
f
) или значение (a
>>= f
) Nothing
- и затем также возвращает Nothing
.
join Nothing = Nothing
join (Just Nothing) = Nothing
join (Just x) = x
a >>= f = join (fmap f a)
или, более точно:
Nothing >>= _ = Nothing
(Just x) >>= f = f x
Монада состояний предназначена для функций, которые также изменяют некоторое общее состояние - s -> (a, s)
, поэтому аргумент >>=
равен :: a -> s -> (a, s)
.
Название является своего рода неправильным, поскольку State
действительно для функций, изменяющих состояние, а не для состояния - само состояние действительно не имеет интересных свойств, оно просто изменяется.
Например:
pop :: [a] -> (a , [a])
pop (h:t) = (h, t)
sPop = state pop -- The module for State exports no State constructor,
-- only a state function
push :: a -> [a] -> ((), [a])
push x l = ((), x : l)
sPush = state push
swap = do a <- sPop
b <- sPop
sPush a
sPush b
get2 = do a <- sPop
b <- sPop
return (a, b)
getswapped = do swap
get2
, то:
Main*> runState swap [1, 2, 3]
((), [2, 1, 3])
Main*> runState get2 [1, 2, 3]
((1, 2), [1, 2, 3]
Main*> runState (swap >> get2) [1, 2, 3]
((2, 1), [2, 1, 3])
Main*> runState getswapped [1, 2, 3]
((2, 1), [2, 1, 3])
также:
Prelude> runState (return 0) 1
(0, 1)