Эта функция принимает два обычных значения Int
Writer [String] Int
, то есть целое число, обладающее контекстом журнала. В случае, когда параметр b
принимает значение 0
, мы, вместо того чтобы просто вернуть значение a
как результат, используем выражение do
для сборки значения Writer
в качестве результата. Сначала используем функцию tell
, чтобы сообщить об окончании, а затем – функцию return
для возврата значения a
в качестве результата выражения do
. Вместо данного выражения do
мы также могли бы написать следующее:Writer (a, ["Закончили: " ++ show a])
Однако я полагаю, что выражение do
b
не равно 0
. В этом случае мы записываем в журнал, что используем функцию mod
для определения остатка от деления a
и b
. Затем вторая строка выражения do
просто рекурсивно вызывает gcd'
. Вспомните: функция gcd'
теперь, в конце концов, возвращает значение типа Writer
, поэтому вполне допустимо наличие строки gcd' b (a `mod` b)
в выражении do
.Хотя отслеживание выполнения этой новой функции gcd'
Давайте испытаем нашу новую функцию gcd'
Writer [String] Int
, и если мы развернём его из принадлежащего ему newtype
, то получим кортеж. Первая часть кортежа – это результат. Посмотрим, правильный ли он:ghci> fst $ runWriter (gcd 8 3)
1
Хорошо! Теперь что насчёт журнала? Поскольку журнал является списком строк, давайте используем вызов mapM_ putStrLn
ghci> mapM_ putStrLn $ snd $ runWriter (gcd 8 3)
8 mod 3 = 2
3 mod 2 = 1
2 mod 1 = 0
Закончили: 1
Даже удивительно, как мы могли изменить наш обычный алгоритм на тот, который сообщает, что он делает по мере развития, просто превращая обычные значения в монадические и возлагая беспокойство о записях в журнал на реализацию оператора >>=
Writer
!.. Мы можем добавить механизм журналирования почти в любую функцию. Всего лишь заменяем обычные значения значениями типа Writer
, где мы хотим, и превращаем обычное применение функции в вызов оператора >>=
(или выражения do
, если это повышает «читабельность»).Неэффективное создание списков
При использовании монады Writer
++
в качестве реализации метода mappend
, а использование данного оператора для присоединения чего-либо в конец списка заставляет программу существенно медлить, если список длинный.В нашей функции gcd'
a ++ (b ++ (c ++ (d ++ (e ++ f))))
Списки – это структура данных, построение которой происходит слева направо, и это эффективно, поскольку мы сначала полностью строим левую часть списка и только потом добавляем более длинный список справа. Но если мы невнимательны, то использование монады Writer
((((a ++ b) ++ c) ++ d) ++ e) ++ f
Здесь связывание происходит в направлении налево, а не направо. Это неэффективно, поскольку каждый раз, когда функция хочет добавить правую часть к левой, она должна построить левую часть полностью, с самого начала!
Следующая функция работает аналогично функции gcd'
import Control.Monad.Writer
gcdReverse :: Int –> Int –> Writer [String] Int
gcdReverse a b
| b == 0 = do
tell ["Закончили: " ++ show a]
return a
| otherwise = do
result <– gcdReverse b (a `mod` b)
tell [show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b)]
return result
Сначала она производит рекурсивный вызов и привязывает его значение к значению result