Что если бы это сопоставление с образцом окончилось неуспешно? Когда сопоставление с образцом в функции оканчивается не успешно, происходит сопоставление со следующим образцом. Если сопоставление проходит по всем образцам для данной функции с невыполнением их условий, выдаётся ошибка и происходит аварийное завершение работы программы. С другой стороны, сопоставление с образцом, окончившееся неудачей в выражениях let
let
отсутствует механизм прохода к следующему образцу при невыполнении условия.Когда сопоставление с образцом в выражении do
fail
(являющаяся частью класса типов Monad
) позволяет ему вернуть в результате неудачу в контексте текущей монады, вместо того чтобы привести к аварийному завершению работы программы. Вот реализация функции по умолчанию:fail :: (Monad m) => String –> m a
fail msg = error msg
Так что по умолчанию она действительно заставляет программу завершаться аварийно. Но монады, содержащие в себе контекст возможной неудачи (как тип Maybe
Maybe
она реализована следующим образом:fail _ = Nothing
Она игнорирует текст сообщения об ошибке и производит значение Nothing
Maybe
, записанном в нотации do
, результат всего значения будет равен Nothing
. Предпочтительнее, чтобы ваша программа завершила свою работу неаварийно. Вот выражение do
, включающее сопоставление с образцом, которое обречено на неудачу:wopwop :: Maybe Char
wopwop = do
(x:xs) <– Just ""
return x
Сопоставление с образцом оканчивается неуспешно, поэтому эффект аналогичен тому, как если бы вся строка с образцом была заменена значением Nothing
ghci> wopwop
Nothing
Неуспешно окончившееся сопоставление с образцом вызвало неуспех только в контексте нашей монады, вместо того чтобы вызвать неуспех на уровне всей программы. Очень мило!..
Списковая монада
До сих пор вы видели, как значения типа Maybe
>>=
для передачи их функциям. В этом разделе мы посмотрим, как использовать монадическую сторону списков, чтобы внести в код недетерминированность в ясном и «читабельном» виде.В главе 11 мы говорили о том, каким образом списки представляют недетерминированные значения, когда они используются как аппликативные функторы. Значение вроде 5 является детерминированным – оно имеет только один результат, и мы точно знаем, какой он. С другой стороны, значение вроде [3,8,9]
ghci> (*) <$> [1,2,3] <*> [10,100,1000]
[10,100,1000,20,200,2000,30,300,3000]
В окончательный список включаются все возможные комбинации умножения элементов из левого списка на элементы правого. Когда дело касается недетерминированности, у нас есть много вариантов выбора, поэтому мы просто пробуем их все. Это означает, что результатом тоже является недетерминированное значение, но оно содержит намного больше результатов.
Этот контекст недетерминированности очень красиво переводится в монады. Вот как выглядит экземпляр класса Monad
instance Monad [] where
return x = [x]
xs >>= f = concat (map f xs)
fail _ = []
Как вы знаете, функция return
pure
, и вы уже знакомы с функцией return
для списков. Она принимает значение и помещает его в минимальный контекст по умолчанию, который по-прежнему возвращает это значение. Другими словами, функция return
создаёт список, который содержит только одно это значение в качестве своего результата. Это полезно, когда нам нужно просто обернуть обычное значение в список, чтобы оно могло взаимодействовать с недетерминированными значениями.Суть операции >>=
>>=
не была бы столь полезна: после первого применения контекст был бы утрачен.Давайте попробуем передать функции недетерминированное значение:
ghci> [3,4,5] >>= \x –> [x,-x]
[3,-3,4,-4,5,-5]