fail _ = Nothing
Функция return
аналогична функции pure
, так что для работы с ней не нужно большого ума. Мы делаем то же, что мы делали в классе типов Applicative
, и оборачиваем в конструктор Just
. Операция >>=
аналогична нашей функции applyMaybe
. Когда мы передаём значение типа Maybe a
нашей функции, то запоминаем контекст и возвращаем значение Nothing
, если значением слева является Nothing
. Ещё раз: если значение отсутствует, нет способа применить к нему функцию. Если это значение Just
, мы берём то, что находится внутри, и применяем к этому функцию f
.
Мы можем поиграть с типом Maybe
как с монадой:
ghci> return "ЧТО" :: Maybe String
Just "ЧТО"
ghci> Just 9 >>= \x –> return (x*10)
Just 90
ghci> Nothing >>= \x –> return (x*10)
Nothing
В первой строке нет ничего нового или захватывающего, поскольку мы уже использовали функцию pure
с типом Maybe
, и мы знаем, что функция return
– это просто функция pure
под другим именем.
Следующая пара строк демонстрирует операцию >>=
уже поинтереснее. Обратите внимание: когда мы передавали значение Just 9
анонимной функции \x –> return (x*10)
, то параметр x
принимал значение 9
внутри функции. Выглядит это так, будто мы могли извлечь значение из обёртки Maybe
без сопоставления с образцом. И мы всё ещё не потеряли контекст нашего значения Maybe
, потому что когда оно равно Nothing
, результатом использования операции >>=
тоже будет Nothing
.
Прогулка по канату
Теперь, когда вы знаете, как передавать значение типа Maybe a
функции типа a –> Maybe b
, учитывая контекст возможной неудачи в вычислениях, давайте посмотрим, как можно многократно использовать операцию >>=
для обработки
вычислений нескольких значений Maybe a
.
Пьер решил сделать рабочий перерыв на рыбной ферме и попробовать заняться канатоходством. На удивление, ему это неплохо удаётся, но есть одна проблема: на балансировочный шест приземляются птицы! Они прилетают, немного отдыхают, болтают со своими пернатыми друзьями, а затем срываются в поисках хлебных крошек. Это не сильно беспокоило бы Пьера, будь количество птиц c левой стороны шеста всегда равным количеству птиц с правой стороны. Но порой всем птицам почему-то больше нравится одна сторона. В результате канатоходец теряет равновесие и падает (не волнуйтесь, он использует сетку безопасности!).
Давайте предположим, что Пьер удержит равновесие, если количество птиц на левой стороне шеста и на правой стороне шеста разнится в пределах трёх. Покуда, скажем, на правой стороне одна птица, а на левой – четыре, всё в порядке. Но стоит пятой птице опуститься на левую сторону, канатоходец теряет равновесие и кубарем летит вниз.
Мы сымитируем посадку и улёт птиц с шеста и посмотрим, останется ли Пьер на канате после некоторого количества прилётов и улётов птиц. Например, нам нужно увидеть, что произойдёт с Пьером, если первая птица прилетит на левую сторону, затем четыре птицы займут правую, а потом птица, которая была на левой стороне, решит улететь.
Код, код, код
Мы можем представить шест в виде простой пары целых чисел. Первый компонент будет обозначать количество птиц на левой стороне, а второй – количество птиц на правой:
type Birds = Int
type Pole = (Birds, Birds)
Сначала мы создали синоним типа для Int
, названный Birds
, потому что мы используем целые числа для представления количества имеющихся птиц. Затем создали синоним типа (Birds
, Birds
) и назвали его Pole
(учтите: это означает «шест» – ничего общего ни с поляками, ни с человеком по имени Поль).
А теперь как насчёт того, чтобы добавить функции, которые принимают количество птиц и производят их приземление на одной стороне шеста или на другой?
landLeft :: Birds –> Pole –> Pole
landLeft n (left, right) = (left + n, right)
landRight :: Birds –> Pole –> Pole
landRight n (left, right) = (left, right + n)
Давайте проверим их:
ghci> landLeft 2 (0, 0)
(2,0)
ghci> landRight 1 (1, 2)
(1,3)
ghci> landRight (-1) (1,2)
(1,1)
Чтобы заставить птиц улететь, мы просто произвели приземление отрицательного количества птиц на одной стороне. Поскольку приземление птицы на Pole
возвращает Pole
, мы можем сцепить применения функций landLeft
и landRight
:
ghci> landLeft 2 (landRight 1 (landLeft 1 (0, 0)))
(3,1)