>filter :: (a –> Bool) –> [a] –> [a]
Предикат берёт элемент списка и возвращает значение типа >Bool
. А вдруг возвращённое им значение типа >Bool
было на самом деле монадическим? Что если к нему был приложен контекст?.. Например, каждое значение >True
или >False
, произведённое предикатом, имело также сопутствующее моноидное значение вроде >["Принято число 5"]
или >["3 слишком мало"]
? Если бы это было так, мы бы ожидали, что к результирующему списку тоже прилагается журнал всех журнальных значений, которые были произведены на пути. Поэтому если бы к списку, возвращённому предикатом, возвращающим значение типа >Bool
, был приложен контекст, мы ожидали бы, что к результирующему списку тоже прикреплён некоторый контекст. Иначе контекст, приложенный к каждому значению типа >Bool
, был бы утрачен.
Функция >filterM
из модуля >Control.Monad
делает именно то, что мы хотим! Её тип таков:
>filterM :: (Monad m) => (a –> m Bool) –> [a] –> m [a]
Предикат возвращает монадическое значение, результат которого – типа >Bool
, но поскольку это монадическое значение, его контекст может быть всем чем угодно, от возможной неудачи до недетерминированности и более! Чтобы обеспечить отражение контекста в окончательном результате, результат тоже является монадическим значением.
Давайте возьмём список и оставим только те значения, которые меньше >4
. Для начала мы используем обычную функцию >filter
:
>ghci> filter (\x –> x < 4) [9,1,5,2,10,3]
>[1,2,3]
Это довольно просто. Теперь давайте создадим предикат, который помимо представления результата >True
или >False
также предоставляет журнал своих действий. Конечно же, для этого мы будем использовать монаду >Writer
:
>keepSmall :: Int –> Writer [String] Bool
>keepSmall x
> | x < 4 = do
> tell ["Сохраняем " ++ show x]
> return True
> | otherwise = do
> tell [show x ++ " слишком велико, выбрасываем"]
> return False
Вместо того чтобы просто возвращать значение типа >Bool
, функция возвращает значение типа >Writer [String] Bool
. Это монадический предикат. Звучит необычно, не так ли? Если число меньше числа >4
, мы сообщаем, что оставили его, а затем возвращаем значение >True
.
Теперь давайте передадим его функции >filterM
вместе со списком. Поскольку предикат возвращает значение типа >Writer
, результирующий список также будет значением типа >Writer
.
>ghci> fst $ runWriter $ filterM keepSmall [9,1,5,2,10,3]
>[1,2,3]
Проверяя результат результирующего значения монады >Writer
, мы видим, что всё в порядке. Теперь давайте распечатаем журнал и посмотрим, что у нас есть:
>ghci> mapM_ putStrLn $ snd $ runWriter $ filterM keepSmall [9,1,5,2,10,3]