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

>(False,"Небольшая банда.Размер банды сравнён с 9.")

>ghci> (30, "Бешеный взвод.") `applyLog` isBigGang

>(True,"Бешеный взвод.Размер банды сравнён с 9.")

Результаты аналогичны предшествующим, только теперь количеству бандитов сопутствует журнал, который включён в окончательный журнал.

Вот ещё несколько примеров использования >applyLog:

>ghci> ("Тобин","Вне закона.") `applyLog` (\x –> (length x "Длина."))

>(5,"Вне закона.Длина.")

>ghci> ("Котопёс","Вне закона.") `applyLog` (\x –> (length x "Длина."))

>(7,"Вне закона.Длина.")

Смотрите, как внутри анонимной функции образец >x является просто нормальной строкой, а не кортежем, и как функция >applyLog заботится о добавлении записей журнала.

Моноиды приходят на помощь

Убедитесь, что вы на данный момент знаете, что такое моноиды!

Прямо сейчас функция >applyLog принимает значения типа >(a,String), но есть ли смысл в том, чтобы тип журнала был >String? Он использует операцию ++ для добавления записей журнала – не будет ли это работать и в отношении любого типа списков, не только списка символов? Конечно же, будет! Мы можем пойти дальше и изменить тип этой функции на следующий:

>applyLog :: (a,[c]) –> (a –> (b,[c])) –> (b,[c])

Теперь журнал является списком. Тип значений, содержащихся в списке, должен быть одинаковым как для изначального списка, так и для списка, который возвращает функция; в противном случае мы не смогли бы использовать операцию >++ для «склеивания» их друг с другом.

Сработало бы это для строк байтов? Нет причины, по которой это не сработало бы! Однако тип, который у нас имеется, работает только со списками. Похоже, что нам пришлось бы создать ещё одну функцию >applyLog для строк байтов. Но подождите! И списки, и строки байтов являются моноидами. По существу, те и другие являются экземплярами класса типов >Monoid, а это значит, что они реализуют функцию >mappend. Как для списков, так и для строк байтов функция >mappend производит конкатенацию. Смотрите:

>ghci> [1,2,3] `mappend` [4,5,6]

>[1,2,3,4,5,6]

>ghci> B.pack [99,104,105] `mappend` B.pack [104,117,97,104,117,97]

>Chunk "chi" (Chunk "huahua" Empty)

Круто! Теперь наша функция >applyLog может работать для любого моноида. Мы должны изменить тип, чтобы отразить это, а также реализацию, потому что следует заменить вызов операции >++ вызовом функции >mappend:

>applyLog :: (Monoid m) => (a,m) –> (a –> (b,m)) –> (b,m)

>applyLog (x,log) f = let (y,newLog) = f x

>                     in (y,log `mappend` newLog)

Поскольку сопутствующее значение теперь может быть любым моноидным значением, нам больше не нужно думать о кортеже как о значении и журнале, но мы можем думать о нём как о значении с сопутствующим моноидным значением. Например, у нас может быть кортеж, в котором есть имя предмета и цена предмета в виде моноидного значения. Мы просто используем определение типа