Изучай Haskell во имя добра! (Липовача) - страница 269

>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]