должен быть таким:
>gcd' :: Int –> Int –> Writer [String] Int
Всё, что осталось сделать, – снабдить нашу функцию журнальными значениями. Вот код:
>import Control.Monad.Writer
>gcd' :: Int –> Int –> Writer [String] Int
>gcd' a b
> | b == 0 = do
> tell ["Закончили: " ++ show a]
> return a
> | otherwise = do
> tell [show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b)]
> gcd' b (a `mod` b)
Эта функция принимает два обычных значения >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