Категории
Самые читаемые
PochitayKnigi » Компьютеры и Интернет » Программирование » Изучай Haskell во имя добра! - Миран Липовача

Изучай Haskell во имя добра! - Миран Липовача

Читать онлайн Изучай Haskell во имя добра! - Миран Липовача

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 70 71 72 73 74 75 76 77 78 ... 96
Перейти на страницу:

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)

Вспомните, что функция landLeft 2 имеет тип Pole –> Maybe Pole. Мы не можем просто передать ей значение типа Maybe Pole, которое является результатом вызова функции landRight 1 (0, 0), поэтому используем операцию >>=, чтобы взять это значение с контекстом и отдать его функции landLeft 2. Операция >>= действительно позволяет нам обрабатывать значения типа Maybe как значения с контекстом. Если мы передадим значение Nothing в функцию landLeft 2, результатом будет Nothing, и неудача будет распространена:

ghci> Nothing >>= landLeft 2

Nothing

Используя это, мы теперь можем помещать в цепочку приземления, которые могут окончиться неуспешно, потому что оператор >>= позволяет нам передавать монадическое значение функции, которая принимает обычное значение. Вот последовательность приземлений птиц:

ghci> return (0, 0) >>= landRight 2 >>= landLeft 2 >>= landRight 2

Just (2,4)

Вначале мы использовали функцию return, чтобы взять шест и обернуть его в конструктор Just. Мы могли бы просто применить выражение landRight 2 к значению (0, 0) – это было бы то же самое, – но так можно добиться большего единообразия, используя оператор >>= для каждой функции. Выражение Just (0, 0) передаётся в функцию landRight 2, что в результате даёт результат Just (0, 2). Это значение в свою очередь передаётся в функцию landLeft 2, что в результате даёт новый результат (2, 2), и т. д.

Помните следующий пример, прежде чем мы ввели возможность неудачи в инструкции Пьера?

ghci> (0, 0) -: landLeft 1 -: landRight 4 -: landLeft (-1) -: landRight (-2)

(0,2)

Он не очень хорошо симулировал взаимодействие канатоходца с птицами. В середине его равновесие было нарушено, но результат этого не отразил. Давайте теперь исправим это, используя монадическое применение (оператор >>=) вместо обычного:

ghci> return (0, 0) >>= landLeft 1 >>= landRight 4 >>= landLeft (-1) >>= landRight (-2)

Nothing

Окончательный результат представляет неудачу, чего мы и ожидали. Давайте посмотрим, как этот результат был получен:

1. Функция return помещает значение (0, 0) в контекст по умолчанию, превращая значение в Just (0, 0).

2. Происходит вызов выражения Just (0, 0) >>= landLeft 1. Поскольку значение Just (0, 0) является значением Just, функция landLeft 1 применяется к (0, 0), что в результате даёт результат Just (1, 0), потому что птицы всё ещё находятся в относительном равновесии.

3. Имеет место вызов выражения Just (1, 0) >>= landRight 4, и результатом является выражение Just (1, 4), поскольку равновесие птиц пока ещё не затронуто, хотя Пьер уже удерживается с трудом.

4. Выражение Just (1, 4) передаётся в функцию landLeft (–1). Это означает, что имеет место вызов landLeft (–1) (1, 4). Теперь ввиду особенностей работы функции landLeft в результате это даёт значение Nothing, так как результирующий шест вышел из равновесия.

5. Теперь, поскольку у нас есть значение Nothing, оно передаётся в функцию landRight (–2), но так как это Nothing, результатом автоматически становится Nothing, поскольку нам не к чему применить эту функцию.

Мы не смогли бы достигнуть этого, просто используя Maybe в качестве аппликативного функтора. Если вы попробуете так сделать, то застрянете, поскольку аппликативные функторы не очень-то позволяют аппликативным значениям взаимодействовать друг с другом. Их в лучшем случае можно использовать как параметры для функции, применяя аппликативный стиль.

Аппликативные операторы извлекут свои результаты и передадут их функции в соответствующем для каждого аппликативного функтора виде, а затем соберут окончательное аппликативное значение, но взаимодействие между ними не особенно заметно. Здесь, однако, каждый шаг зависит от результата предыдущего шага. Во время каждого приземления возможный результат предыдущего шага исследуется, а шест проверяется на равновесие. Это определяет, окончится ли посадка успешно либо неуспешно.

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

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

1 ... 70 71 72 73 74 75 76 77 78 ... 96
Перейти на страницу:
Тут вы можете бесплатно читать книгу Изучай Haskell во имя добра! - Миран Липовача.
Комментарии