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

>import Control.Monad.Writer


>gcdReverse :: Int –> Int –> Writer (DiffList String) Int

>gcdReverse a b

>   | b == 0 = do

>      tell (toDiffList ["Закончили: " ++ show a])

>      return a

>   | otherwise = do

>      result <– gcdReverse b (a `mod` b)

>      tell (toDiffList [show a ++ " mod " ++ show b ++ " = "

>                        ++ show (a `mod` b)])

>      return result

Нам всего лишь нужно было изменить тип моноида с >[String] на >DiffList String, а затем при использовании функции >tell преобразовать обычные списки в разностные с помощью функции >toDiffList. Давайте посмотрим, правильно ли соберётся журнал:

>ghci> mapM_ putStrLn . fromDiffList . snd . runWriter $ gcdReverse 110 34

>Закончили: 2

>8 mod 2 = 0

>34 mod 8 = 2

>110 mod 34 = 8

Мы выполняем вызов выражения >gcdReverse 110 34, затем используем функцию >runWriter, чтобы развернуть его результат из >newtype, потом применяем к нему функцию >snd, чтобы просто получить журнал, далее – функцию >fromDiffList, чтобы преобразовать его в обычный список, и в заключение выводим его записи на экран.

Сравнение производительности

Чтобы почувствовать, насколько разностные списки могут улучшить вашу производительность, рассмотрите следующую функцию. Она просто в обратном направлении считает от некоторого числа до нуля, но производит записи в журнал в обратном порядке, как функция >gcdReverse, чтобы числа в журнале на самом деле считались в прямом направлении.

>finalCountDown :: Int –> Writer (DiffList String) ()

>finalCountDown 0 = tell (toDiffList ["0"])

>finalCountDown x = do

>   finalCountDown (x-1)

>   tell (toDiffList [show x])

Если мы передаём ей значение >0, она просто записывает это значение в журнал. Для любого другого числа она сначала вычисляет предшествующее ему число в обратном направлении до >0, а затем добавляет это число в конец журнала. Поэтому если мы применим функцию >finalCountDown к значению >100, строка >"100" будет идти в журнале последней.

Если вы загрузите эту функцию в интерпретатор GHCi и примените её к большому числу, например к значению 500 000, то увидите, что она быстро начинает счёт от >0 и далее:

>ghci> mapM_ putStrLn . fromDiffList .snd . runWriter $ finalCountDown 500000

>0

>1

>2

>...

Однако если вы измените её, чтобы она использовала обычные списки вместо разностных, например, так:

>finalCountDown :: Int –> Writer [String] ()

>finalCountDown 0 = tell ["0"]

>finalCountDown x = do

>   finalCountDown (x-1)

>   tell [show x]

а затем скажете интерпретатору GHCi, чтобы он начал отсчёт:

>ghci> mapM_ putStrLn . snd . runWriter $ finalCountDown 500000

вы увидите, что вычисления идут очень медленно.