ghci> return (0, 0) >>= landLeft 1 >>= banana >>= landRight 1
Nothing
Функции banana
передаётся значение Just (1, 0)
, но она всегда производит значение Nothing
, которое заставляет всё выражение возвращать в результате Nothing
. Какая досада!..
Вместо создания функций, которые игнорируют свои входные данные и просто возвращают предопределённое монадическое значение, мы можем использовать функцию >>
. Вот её реализация по умолчанию:
(>>) :: (Monad m) => m a –> m b –> m b
m >> n = m >>= \_ –> n
Обычно передача какого-либо значения функции, которая игнорирует свой параметр и всегда возвращает некое предопределённое значение, всегда даёт в результате это предопределённое значение. При использовании монад, однако, нужно принимать во внимание их контекст и значение. Вот как функция >>
действует при использовании с типом Maybe
:
ghci> Nothing >> Just 3
Nothing
ghci> Just 3 >> Just 4
Just 4
ghci> Just 3 >> Nothing
Nothing
Если мы заменим оператор >> на вызов >>= \_ –>
, легко увидеть, что происходит.
Мы можем заменить нашу функцию banana
в цепочке на оператор >>
и следующее за ним значение Nothing
, чтобы получить гарантированную и очевидную неудачу:
ghci> return (0, 0) >>= landLeft 1 >> Nothing >>= landRight 1
Nothing
Как бы это выглядело, если бы мы не сделали разумный выбор, обработав значения типа Maybe
как значения с контекстом неудачи и передав их функциям? Вот какой была бы последовательность приземлений птиц:
routine :: Maybe Pole
routine = case landLeft 1 (0, 0) of
Nothing –> Nothing
Just pole1 –> case landRight 4 pole1 of
Nothing –> Nothing
Just pole2 –> case landLeft 2 pole2 of
Nothing –> Nothing
Just pole3 –> landLeft 1 pole3
Мы усаживаем птицу слева, а затем проверяем вероятность неудачи и вероятность успеха. В случае неудачи мы возвращаем значение Nothing
. В случае успеха усаживаем птиц справа, а затем повторяем всё сызнова. Превращение этого убожества в симпатичную цепочку монадических применений с использованием функции >>=
является классическим примером того, как монада Maybe
экономит массу времени, когда вам необходимо последовательно выполнить вычисления, основанные на вычислениях, которые могли окончиться неуспешно.
Обратите внимание, каким образом реализация операции >>=
для типа Maybe
отражает именно эту логику, когда проверяется, равно ли значение Nothing
, и действие производится на основе этих сведений. Если значение равно Nothing
, она незамедлительно возвращает результат Nothing
. Если значение не равно Nothing
, она продолжает работу с тем, что находится внутри конструктора Just
.
В этом разделе мы рассмотрели, как некоторые функции работают лучше, когда возвращаемые ими значения поддерживают неудачу. Превращая эти значения в значения типа Maybe
и заменяя обычное применение функций вызовом операции >>=
, мы практически даром получили механизм обработки вычислений, которые могут оканчиваться неудачно. Причина в том, что операция >>=
должна сохранять контекст значения, к которому она применяет функции. В данном случае контекстом являлось то, что наши значения были значениями с неуспехом в вычислениях. Поэтому когда мы применяли к таким значениям функции, всегда учитывалась вероятность неуспеха.
Нотация do
Монады в языке Haskell настолько полезны, что они обзавелись своим собственным синтаксисом, который называется «do
в главе 8, когда мы использовали её для объединения нескольких действий ввода-вывода. Как оказывается, нотация do
предназначена не только для системы ввода-вывода, но может использоваться для любой монады. Её принцип остаётся прежним: последовательное «склеивание» монадических значений.
Рассмотрим этот знакомый пример монадического применения:
ghci> Just 3 >>= (\x –> Just (show x ++ "!"))
Just "3!"
Это мы уже проходили! Передача монадического значения функции, которая возвращает монадическое значение, – ничего особенного. Заметьте, как параметр x
становится равным значению 3
внутри анонимной функции, когда мы выполняем код. Как только мы внутри этой анонимной функции, это просто обычное значение, а не монадическое. А что если бы у нас был ещё один вызов оператора >>=
внутри этой функции? Посмотрите:
ghci> Just 3 >>= (\x –> Just "!" >>= (\y –> Just (show x ++ y)))
Just "3!"