Когда мы применяем функцию landLeft 1
к значению (0, 0)
, у нас получается результат (1, 0)
. Затем мы усаживаем птицу на правой стороне, что даёт в результате (1, 1)
. Наконец, две птицы приземляются на левой стороне, что даёт в результате (3, 1)
. Мы применяем функцию к чему-либо, сначала записывая функцию, а затем её параметр, но здесь было бы лучше, если бы первым шел шест, а потом функция посадки. Предположим, мы создали вот такую функцию:
x -: f = f x
Можно применять функции, сначала записывая параметр, а затем функцию:
ghci> 100 -: (*3)
300
ghci> True -: not
False
ghci> (0, 0) -: landLeft 2
(2,0)
Используя эту форму, мы можем многократно производить приземление птиц на шест в более «читабельном» виде:
ghci> (0, 0) -: landLeft 1 -: landRight 1 -: landLeft 2
(3,1)
Круто!.. Эта версия эквивалентна предыдущей, где мы многократно усаживали птиц на шест, но выглядит она яснее. Здесь очевиднее, что мы начинаем с (0, 0)
, а затем усаживаем одну птицу слева, потом одну – справа, и в довершение две – слева.
Я улечу
Пока всё идёт нормально, но что произойдёт, если десять птиц приземлятся на одной стороне?
ghci> landLeft 10 (0, 3)
(10,3)
Десять птиц с левой стороны и лишь три с правой?! Этого достаточно, чтобы отправить в полёт самого Пьера!.. Довольно очевидная вещь. Но что если бы у нас была примерно такая последовательность посадок:
ghci> (0, 0) -: landLeft 1 -: landRight 4 -: landLeft (-1) -: landRight (-2)
(0,2)
Может показаться, что всё хорошо, но если вы проследите за шагами, то увидите, что на правой стороне одновременно находятся четыре птицы – а на левой ни одной! Чтобы исправить это, мы должны ещё раз взглянуть на наши функции landLeft
и landRight
.
Необходимо дать функциям landLeft
и landRight
возможность завершаться неуспешно. Нам нужно, чтобы они возвращали новый шест, если равновесие поддерживается, но завершались неуспешно, если птицы приземляются неравномерно. И какой способ лучше подойдёт для добавления к значению контекста неудачи, чем использование типа Maybe
? Давайте переработаем эти функции:
landLeft :: Birds –> Pole –> Maybe Pole
landLeft n (left,right)
| abs ((left + n) - right) < 4 = Just (left + n, right)
| otherwise = Nothing
landRight :: Birds –> Pole –> Maybe Pole
landRight n (left,right)
| abs (left - (right + n)) < 4 = Just (left, right + n)
| otherwise = Nothing
Вместо того чтобы вернуть значение типа Pole
, эти функции теперь возвращают значения типа Maybe Pole
. Они по-прежнему принимают количество птиц и прежний шест, как и ранее, но затем проверяют, выведет ли Пьера из равновесия приземление такого количества птиц. Мы используем охранные выражения, чтобы проверить, меньше ли разница в количестве птиц на новом шесте, чем 4. Если меньше, оборачиваем новый шест в конструктор Just
и возвращаем это. Если не меньше, возвращаем значение Nothing
, сигнализируя о неудаче.
Давайте опробуем этих деток:
ghci> landLeft 2 (0, 0)
Just (2,0)
ghci> landLeft 10 (0, 3)
Nothing
Когда мы приземляем птиц, не выводя Пьера из равновесия, мы получаем новый шест, обёрнутый в конструктор Just
. Но когда значительное количество птиц в итоге оказывается на одной стороне шеста, в результате мы получаем значение Nothing
. Всё это здорово, но, похоже, мы потеряли возможность многократного приземления птиц на шесте! Выполнить landLeft 1 (landRight 1 (0, 0))
больше нельзя, потому что когда landRight 1
применяется к (0, 0)
, мы получаем значение не типа Pole
, а типа Maybe Pole
. Функция landLeft 1
принимает параметр типа Pole
, а не Maybe Pole
.
Нам нужен способ получения Maybe Pole
и передачи его функции, которая принимает Pole
и возвращает Maybe Pole
. К счастью, у нас есть операция >>=
, которая делает именно это для типа Maybe
. Давайте попробуем:
ghci> landRight 1 (0, 0) >>= landLeft 2
Just (2,1)