Вы можете увидеть данную монаду m
как набор / семейство (или область, домен и т. Д.) Из действий (вспомните оператор C). Монада m
определяет вид (побочных) эффектов, которые могут иметь ее действия:
- с помощью
[]
вы можете определить действия, которые могут разветвлять их выполнение в разных "независимых параллельных мирах";
- с помощью
Either Foo
вы можете определить действия, которые могут завершиться с ошибками типа Foo
;
- с помощью
IO
вы можете определить действия, которые могут иметь побочные эффекты для «внешнего мира» (доступ к файлам, сети, процессы запуска, выполнение HTTP GET ...);
- у вас может быть монада с эффектом «случайности» (см. Пакет
MonadRandom
);
- Вы можете определить монаду, чьи действия могут сделать ход в игре (скажем, шахматы, Го ...) и получить ход от оппонента, но не могут писать в вашу файловую систему или что-либо еще.
Резюме
Если m
- это монада, m a
- это действие , которое выдает результат / вывод типа a
.
Операторы >>
и >>=
используются для создания более сложных действий из более простых:
a >> b
- это макро-действие, которое выполняет действие a
, а затем действие b
;
a >> a
выполняет действие a
, а затем снова действие a
;
- с
>>=
второе действие может зависеть от выхода первого.
Точное значение того, что является действием и что выполняет действие, а затем еще одно , зависит от монады: каждая монада определяет императивный подъязык с некоторыми особенностями / эффектами.
Простое секвенирование (>>
)
Допустим, у вас есть заданная монада M
и некоторые действия incrementCounter
, decrementCounter
, readCounter
:
instance M Monad where ...
-- Modify the counter and do not produce any result:
incrementCounter :: M ()
decrementCounter :: M ()
-- Get the current value of the counter
readCounter :: M Integer
Теперь мы хотели бы сделать что-то интересное с этими действиями. Первое, что мы хотели бы сделать с этими действиями, это упорядочить их. Как и в С скажем, мы хотели бы сделать:
// This is C:
counter++;
counter++;
Мы определяем «оператор секвенирования» >>
. Используя этот оператор, мы можем написать:
incrementCounter >> incrementCounter
Какой тип "incrementCounter >> incrementCounter"?
Это действие, выполненное из двух меньших действий, как в C, вы можете написать составные операторы из атомарных операторов:
// This is a macro statement made of several statements
{
counter++;
counter++;
}
// and we can use it anywhere we may use a statement:
if (condition) {
counter++;
counter++;
}
может иметь тот же эффект, что и его подгруппы;
не дает никакого вывода / результата.
Итак, нам бы хотелось, чтобы incrementCounter >> incrementCounter
был типа M ()
: (макро) действие с такими же возможными эффектами, но без вывода.
В целом, с учетом двух действий:
action1 :: M a
action2 :: M b
мы определяем a >> b
как макро-действие, которое получается , выполняющим (что бы это ни значило в нашей области действия) a
, затем b
и выдает в качестве результата результат выполнение второго действия. Тип >>
:
(>>) :: M a -> M b -> M b
или более широко:
(>>) :: (Monad m) => m a -> m b -> m b
Мы можем определить большую последовательность действий из более простых:
action1 >> action2 >> action3 >> action4
Входы и выходы (>>=
)
Мы хотели бы иметь возможность увеличивать что-то еще на 1 за раз:
incrementBy 5
Мы хотим внести некоторый вклад в наши действия, для этого мы определяем функцию incrementBy
, которая принимает Int
и производит действие:
incrementBy :: Int -> M ()
Теперь мы можем написать что-то вроде:
incrementCounter >> readCounter >> incrementBy 5
Но у нас нет способа передать вывод readCounter
в incrementBy
. Для этого нужна чуть более мощная версия нашего оператора секвенирования. Оператор >>=
может передавать выходные данные данного действия в качестве входных данных для следующего действия. Мы можем написать:
readCounter >>= incrementBy
Это действие, которое выполняет действие readCounter
, передает свой вывод в функцию incrementBy
и затем выполняет результирующее действие.
Тип >>=
:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
A (частичный) пример
Допустим, у меня есть монада Prompt
, которая может отображать только информацию (текст) для пользователя и запрашивать информацию для пользователя:
-- We don't have access to the internal structure of the Prompt monad
module Prompt (Prompt(), echo, prompt) where
-- Opaque
data Prompt a = ...
instance Monad Prompt where ...
-- Display a line to the CLI:
echo :: String -> Prompt ()
-- Ask a question to the user:
prompt :: String -> Prompt String
Давайте попробуем определить promptBoolean message
действия, которые задают вопрос и выдают логическое значение.
Мы используем действие (message ++ "[y/n]")
приглашения и передаем его вывод в функцию f
:
f "y"
должно быть действием, которое ничего не делает, но производит True
в качестве вывода;
f "n"
должно быть действием, которое ничего не делаетвыдайте False
как вывод;
все остальное должно перезапустить действие (повторите действие);
promptBoolean
будет выглядеть такthis:
-- Incomplete version, some bits are missing:
promptBoolean :: String -> M Boolean
promptBoolean message = prompt (message ++ "[y/n]") >>= f
where f result = if result == "y"
then ???? -- We need here an action which does nothing but produce `True` as output
else if result=="n"
then ???? -- We need here an action which does nothing but produce `False` as output
else echo "Input not recognised, try again." >> promptBoolean
Создание значения без эффекта (return
)
Чтобы заполнить недостающие биты в нашей функции promptBoolean
, нам нужен способ представления фиктивных действий безлюбой побочный эффект, но который выдает только заданное значение:
-- "return 5" is an action which does nothing but outputs 5
return :: (Monad m) => a -> m a
, и теперь мы можем выписать promptBoolean
функцию:
promptBoolean :: String -> Prompt Boolean
promptBoolean message :: prompt (message ++ "[y/n]") >>= f
where f result = if result=="y"
then return True
else if result=="n"
then return False
else echo "Input not recognised, try again." >> promptBoolean message
, составив эти два простых действия (promptBoolean
, echo
) мы можем определить любой вид диалога между пользователем и вашей программой (действия программы сдерживаютсяминистическая, так как наша монада не имеет «эффекта случайности»).
promptInt :: String -> M Int
promptInt = ... -- similar
-- Classic "guess a number game/dialogue"
guess :: Int -> m()
guess n = promptInt "Guess:" m -> f
where f m = if m == n
then echo "Found"
else (if m > n
then echo "Too big"
then echo "Too small") >> guess n
Операции монады
Монада - это набор действий, которые можно составить с помощью return
и >>=
операторы:
Эти два оператора являются минимальными операторами, необходимыми для определения Monad
.
В Haskell оператор >>
также необходим, но можетфактически получается из >>=
:
(>>): Monad m => m a -> m b -> m b
a >> b = a >>= f
where f x = b
В Haskell также необходим дополнительный оператор fail
, но это действительно взлом (и он может быть удален из Monad
в будущем ).
Это определение Haskell для Monad
:
class Monad m where
return :: m a
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b -- can be derived from (>>=)
fail :: String -> m a -- mostly a hack
Первоклассные действия
Одна замечательная вещь о монадахявляется то, что действия являются первоклассными.Вы можете взять их в переменную, вы можете определить функцию, которая выполняет действия в качестве входных данных и производит некоторые другие действия в качестве выходных данных.Например, мы можем определить оператор while
:
-- while x y : does action y while action x output True
while :: (Monad m) => m Boolean -> m a -> m ()
while x y = x >>= f
where f True = y >> while x y
f False = return ()
Сводка
A Monad
- это набор действий в некотором домене.Монада / домен определяют виды возможных эффектов.Операторы >>
и >>=
представляют последовательность действий, и монадическое выражение может использоваться для представления любого типа «императивной (под) программы» в вашей (функциональной) программе на Haskell.
Замечательно то, что:
вы можете создать свой собственный Monad
, который поддерживает нужные вам функции и эффекты
вы можете написать свои собственные управляющие структуры (while
, throw
, catch
или более экзотические) как функции, выполняющие действия и сочетающие их каким-либо образом для создания более крупных макро-действий.
MonadRandom
Хорошим способом понимания монад является пакет MonadRandom
.Монада Rand
состоит из действий, выход которых может быть случайным (эффект - случайность). action в этой монаде является некоторой случайной величиной (или, точнее, процессом выборки):
-- Sample an Int from some distribution
action :: Rand Int
Использование Rand
для выполнения некоторых алгоритмов выборки / случайной выборки довольно интересно, потому чтоу вас есть случайные величины в качестве значений первого класса:
-- Estimate mean by sampling nsamples times the random variable x
sampleMean :: Real a => Int -> m a -> m a
sampleMean n x = ...
В этом параметре функция sequence
из Prelude
,
sequence :: Monad m => [m a] -> m [a]
становится
sequence :: [Rand a] -> Rand [a]
Создает случайную величину, полученную путем выборки независимо от списка случайных величин.