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

должен быть таким:

>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