Класс типов Monad

Как и функторы, у которых есть класс типов Functor, и аппликативные функторы, у которых есть класс типов Applicative, монады обладают своим классом типов: Monad! (Ух ты, кто бы мог подумать?)

class Monad m where

   return :: a –> m a

   (>>=) :: m a –> (a –> m b) –> m b

   (>>) :: m a –> m b –> m b

   x >> y = x >>= \_ –> y

   fail :: String –> m a

   fail msg = error msg

В первой строке говорится class Monad m where. Стойте, не говорил ли я, что монады являются просто расширенными аппликативными функторами? Не надлежит ли здесь быть ограничению класса наподобие class (Applicative m) => Monad m where, чтобы тип должен был являться аппликативным функтором, прежде чем он может быть сделан монадой? Ладно, положим, надлежит, – но когда появился язык Haskell, людям не пришло в голову, что аппликативные функторы хорошо подходят для этого языка. Тем не менее будьте уверены: каждая монада является аппликативным функтором, даже если в объявлении класса Monad этого не говорится.

Первой функцией, которая объявлена в классе типов Monad, является return. Она аналогична функции pure, находящейся в классе типов Applicative. Так что, хоть она и называется по-другому, вы уже фактически с ней знакомы. Функция return имеет тип (Monad m) => a –> m a. Она принимает значение и помещает его в минимальный контекст по умолчанию, который по-прежнему содержит это значение. Другими словами, она принимает нечто и оборачивает это в монаду. Мы уже использовали функцию return при обработке действий ввода-вывода (см. главу 8). Там она понадобилась для получения значения и создания фальшивого действия ввода-вывода, которое ничего не делает, а только возвращает это значение. В случае с типом Maybe она принимает значение и оборачивает его в конструктор Just.

ПРИМЕЧАНИЕ. Функция return ничем не похожа на оператор return из других языков программирования, таких как C++ или Java. Она не завершает выполнение функции. Она просто принимает обычное значение и помещает его в контекст.

Следующей функцией является >>=, или связывание. Она похожа на применение функции, но вместо того, чтобы получать обычное значение и передавать его обычной функции, она принимает монадическое значение (то есть значение с контекстом) и передаёт его функции, которая принимает обычное значение, но возвращает монадическое.

Затем у нас есть операция >>. Мы пока не будем обращать на неё большого внимания, потому что она идёт в реализации по умолчанию, и её редко реализуют при создании экземпляров класса Monad. Мы подробно рассмотрим её в разделе «Банан на канате».

Последним методом в классе типов Monad является функция fail. Мы никогда не используем её в нашем коде явно. Вместо этого её использует язык Haskell, чтобы сделать возможным неуспешное окончание вычислений в специальной синтаксической конструкции для монад, с которой вы встретитесь позже. Нам не нужно сейчас сильно беспокоиться по поводу этой функции.

Теперь, когда вы знаете, как выглядит класс типов Monad, давайте посмотрим, каким образом для типа Maybe реализован экземпляр этого класса!

instance Monad Maybe where

   return x = Just x

   Nothing >>= f = Nothing

   Just x >>= f = f x

   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.