Банан на канате
Давайте разработаем функцию, которая игнорирует текущее количество птиц на балансировочном шесте и просто заставляет Пьера поскользнуться и упасть. Мы назовём её banana:
banana :: Pole –> Maybe Pole
banana _ = Nothing
Мы можем поместить эту функцию в цепочку вместе с нашими приземлениями птиц. Она всегда будет вызывать падение канатоходца, поскольку игнорирует всё, что ей передаётся в качестве параметра, и неизменно возвращает неудачу.
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 и заменяя обычное применение функций вызовом операции >>=, мы практически даром получили механизм обработки вычислений, которые могут оканчиваться неудачно. Причина в том, что операция >>= должна сохранять контекст значения, к которому она применяет функции. В данном случае контекстом являлось то, что наши значения были значениями с неуспехом в вычислениях. Поэтому когда мы применяли к таким значениям функции, всегда учитывалась вероятность неуспеха.