Да! Генераторы списков! В нашем примере, использующем нотацию do
, образец n
принимал значения всех результатов из списка [1,2]
. Для каждого такого результата образцу ch
был присвоен результат из списка ['a','b']
, а последняя строка помещала пару (n,
ch)
в контекст по умолчанию (одноэлементный список) для возврата его в качестве результата без привнесения какой-либо дополнительной недетерминированности. В генераторе списка произошло то же самое, но нам не нужно было писать вызов функции return
в конце для возврата пары (n,
ch)
в качестве результата, потому что выводящая часть генератора списка сделала это за нас.
На самом деле генераторы списков являются просто синтаксическим сахаром для использования списков как монад. В конечном счёте генераторы списков и списки, используемые в нотации do
, переводятся в использование операции >>=
для осуществления вычислений, которые обладают недетерминированностью.
Класс MonadPlus и функция guard
Генераторы списков позволяют нам фильтровать наши выходные данные. Например, мы можем отфильтровать список чисел в поиске только тех из них, которые содержат цифру 7
:
ghci> [x | x <– [1..50], '7' `elem` show x]
[7,17,27,37,47]
Мы применяем функцию show
к параметру x
чтобы превратить наше число в строку, а затем проверяем, является ли символ '7'
частью этой строки.
Чтобы увидеть, как фильтрация в генераторах списков преобразуется в списковую монаду, мы должны рассмотреть функцию guard
и класс типов MonadPlus
.
Класс типов MonadPlus
предназначен для монад, которые также могут вести себя как моноиды. Вот его определение:
class Monad m => MonadPlus m where
mzero :: m a
mplus :: m a –> m a –> m a
Функция mzero
является синонимом функции mempty
из класса типов Monoid
, а функция mplus
соответствует функции mappend
. Поскольку списки являются моноидами, а также монадами, их можно сделать экземпляром этого класса типов:
instance MonadPlus [] where
mzero = []
mplus = (++)
Для списков функция mzero
представляет недетерминированное вычисление, которое вообще не имеет результата – неуспешно окончившееся вычисление. Функция mplus
сводит два недетерминированных значения в одно. Функция guard
определена следующим образом:
guard :: (MonadPlus m) => Bool –> m ()
guard True = return ()
guard False = mzero
Функция guard
принимает значение типа Bool
. Если это значение равно True
, функция guard
берёт пустой кортеж ()
и помещает его в минимальный контекст, который по-прежнему является успешным. Если значение типа Bool
равно False
, функция guard
создаёт монадическое значение с неудачей в вычислениях. Вот эта функция в действии:
ghci> guard (5 > 2) :: Maybe ()
Just ()
ghci> guard (1 > 2) :: Maybe ()
Nothing
ghci> guard (5 > 2) :: [()]
[()]
ghci> guard (1 > 2) :: [()]
[]
Выглядит интересно, но чем это может быть полезно? В списковой монаде мы используем её для фильтрации недетерминированных вычислений:
ghci> [1..50] >>= (\x –> guard ('7' `elem` show x) >> return x)
[7,17,27,37,47]
Результат аналогичен тому, что был возвращён нашим предыдущим генератором списка. Как функция guard
достигла этого? Давайте сначала посмотрим, как она функционирует совместно с операцией >>
:
ghci> guard (5 > 2) >> return "клёво" :: [String]
["клёво"]
ghci> guard (1 > 2) >> return "клёво" :: [String]
[]
Если функция guard
срабатывает успешно, результатом, находящимся в ней, будет пустой кортеж. Поэтому дальше мы используем операцию >>
, чтобы игнорировать этот пустой кортеж и предоставить что-нибудь другое в качестве результата. Однако если функция guard
не срабатывает успешно, функция return
впоследствии тоже не сработает успешно, потому что передача пустого списка функции с помощью операции >>=
всегда даёт в результате пустой список. Функция guard
просто говорит: «Если это значение типа Bool
равно False
, верни неуспешное окончание вычислений прямо здесь. В противном случае создай успешное значение, которое содержит в себе значение-пустышку ()
». Всё, что она делает, – позволяет вычислению продолжиться.
Вот предыдущий пример, переписанный в нотации do
:
sevensOnly :: [Int]
sevensOnly = do
x <– [1..50]
guard ('7' `elem` show x)
return x