Банан на канате

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