Нотация do
Монады в языке Haskell настолько полезны, что они обзавелись своим собственным синтаксисом, который называется «нотация do». Вы уже познакомились с нотацией do в главе 8, когда мы использовали её для объединения нескольких действий ввода-вывода. Как оказывается, нотация do предназначена не только для системы ввода-вывода, но может использоваться для любой монады. Её принцип остаётся прежним: последовательное «склеивание» монадических значений.
Рассмотрим этот знакомый пример монадического применения:
ghci> Just 3 >>= (x –> Just (show x ++ "!"))
Just "3!"
Это мы уже проходили! Передача монадического значения функции, которая возвращает монадическое значение, – ничего особенного. Заметьте, как параметр x становится равным значению 3 внутри анонимной функции, когда мы выполняем код. Как только мы внутри этой анонимной функции, это просто обычное значение, а не монадическое. А что если бы у нас был ещё один вызов оператора >>= внутри этой функции? Посмотрите:
ghci> Just 3 >>= (x –> Just "!" >>= (y –> Just (show x ++ y)))
Just "3!"
Ага-а, вложенное использование операции >>=! Во внешней анонимной функции мы передаём значение Just "!" анонимной функции y –> Just (show x ++ y). Внутри этой анонимной функции параметр y становится равным "!". Параметр x по-прежнему равен 3, потому что мы получили его из внешней анонимной функции. Всё это как будто напоминает мне о следующем выражении:
ghci> let x = 3; y = "!" in show x ++ y
"3!"
Главное отличие состоит в том, что значения в нашем примере с использованием оператора >>= являются монадическими. Это значения с контекстом неудачи. Мы можем заменить любое из них на неудачу:
ghci> Nothing >>= (x –> Just "!" >>= (y –> Just (show x ++ y))) Nothing
ghci> Just 3 >>= (x –> Nothing >>= (y –> Just (show x ++ y)))
Nothing
ghci> Just 3 >>= (x –> Just "!" >>= (y –> Nothing))
Nothing
В первой строке передача значения Nothing функции естественным образом даёт в результате Nothing. Во второй строке мы передаём значение Just 3 функции, и параметр x становится равным 3. Но потом мы передаём значение Nothing внутренней анонимной функции, и результатом становится Nothing, что заставляет внешнюю анонимную функцию тоже произвести Nothing в качестве своего результата. Это что-то вроде присвоения значений переменным в выражениях let, только значения, о которых идёт речь, являются монадическими.
Чтобы проиллюстрировать эту идею, давайте запишем следующие строки в сценарий так, чтобы каждое значение типа Maybe занимало свою собственную строку:
foo :: Maybe String
foo = Just 3 >>= (x –>
Just "!">>= (y –>
Just (show x ++ y)))
Чтобы уберечь нас от написания всех этих раздражающих анонимных функций, язык Haskell предоставляет нам нотацию do. Она позволяет нам записать предыдущий кусок кода вот так:
foo :: Maybe String
foo = do
x <– Just 3
y <– Just "!"
Just (show x ++ y)
Могло показаться, что мы получили возможность временно извлекать сущности из значений типа Maybe без необходимости проверять на каждом шагу, являются ли значения типа Maybe значениями в конструкторе Just или значениями Nothing. Вот классно!.. Если какое-либо из значений, которые мы пытаемся извлечь, равно Nothing, всё выражение do в результате вернёт значение Nothing. Мы выдёргиваем наружу их значения (если таковые существуют) и перекладываем необходимость беспокойства о контексте, идущем с этими значениями, на плечи оператора >>=.
Выражения do – это просто другой синтаксис для сцепления монадических значений.