Ответ 1
Моноиды newtypes: нулевое пространство no-op, чтобы сообщить компилятору, что делать
Моноиды отлично переносят существующий тип данных в новый тип, чтобы сообщить компилятору, какую операцию вы хотите сделать.
Поскольку они являются новыми, они не занимают дополнительного места, а применение Sum
или getSum
- это не-op.
Пример: Моноиды в Складном
Здесь существует более чем один способ обобщить foldr (см. этот очень хороший вопрос для самой общей сводки, и этот вопрос, если вам нравятся приведенные ниже примеры деревьев, но вы хотите увидеть наиболее общую складку деревьев).
Один полезный способ (не самый общий способ, но, безусловно, полезный) - сказать что-то складное, если вы можете объединить его элементы в один с двоичной операцией и элементом start/identity. Это точка класса Foldable
.
Вместо явного перехода в двоичную операцию и начальный элемент Foldable
просто запрашивает, что тип данных элемента является экземпляром Monoid.
На первый взгляд это кажется разочаровывающим, потому что мы можем использовать только одну двоичную операцию для каждого типа данных - но следует ли использовать (+)
и 0
для Int
и принимать суммы, но никогда не продукты, или наоборот? Возможно, следует использовать ((+),0)
для Int
и (*),1
для Integer
и конвертировать, когда мы хотим выполнить другую операцию? Разве это не будет тратить много драгоценных процессорных циклов?
Моноиды на помощь
Все, что нам нужно сделать, это тег Sum
, если мы хотим добавить, пометить тегом Product
, если мы хотим размножить, или даже пометить ручным новым типом, если мы хотим сделать что-то другое.
Сложим несколько деревьев! Нам понадобится
fold :: (Foldable t, Monoid m) => t m -> m
-- if the element type is already a monoid
foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
-- if you need to map a function onto the elements first
Расширения DeriveFunctor
и DeriveFoldable
({-# LANGUAGE DeriveFunctor, DeriveFoldable #-}
) великолепны, если вы хотите перевернуть и свернуть свой собственный ADT, не записывая утомительные экземпляры самостоятельно.
import Data.Monoid
import Data.Foldable
import Data.Tree
import Data.Tree.Pretty -- from the pretty-tree package
see :: Show a => Tree a -> IO ()
see = putStrLn.drawVerticalTree.fmap show
numTree :: Num a => Tree a
numTree = Node 3 [Node 2 [],Node 5 [Node 2 [],Node 1 []],Node 10 []]
familyTree = Node " Grandmama " [Node " Uncle Fester " [Node " Cousin It " []],
Node " Gomez - Morticia " [Node " Wednesday " [],
Node " Pugsley " []]]
Пример использования
Строки уже являются моноидами, использующими (++)
и []
, поэтому мы можем с ним fold
, но чисел нет, поэтому мы будем отмечать их с помощью foldMap
.
ghci> see familyTree
" Grandmama "
|
----------------------
/ \
" Uncle Fester " " Gomez - Morticia "
| |
" Cousin It " -------------
/ \
" Wednesday " " Pugsley "
ghci> fold familyTree
" Grandmama Uncle Fester Cousin It Gomez - Morticia Wednesday Pugsley "
ghci> see numTree
3
|
--------
/ | \
2 5 10
|
--
/ \
2 1
ghci> getSum $ foldMap Sum numTree
23
ghci> getProduct $ foldMap Product numTree
600
ghci> getAll $ foldMap (All.(<= 10)) numTree
True
ghci> getAny $ foldMap (Any.(> 50)) numTree
False
Сверните свой собственный моноид
Но что, если мы хотим найти максимальный элемент? Мы можем определить наши собственные моноиды. Я не уверен, почему Max
(и Min
) не включены. Возможно, это потому, что никто не любит думать о Int
, ограниченном или просто не нравится элемент идентификации, основанный на детализации реализации, В любом случае это:
newtype Max a = Max {getMax :: a}
instance (Ord a,Bounded a) => Monoid (Max a) where
mempty = Max minBound
mappend (Max a) (Max b) = Max $ if a >= b then a else b
ghci> getMax $ foldMap Max numTree :: Int -- Int to get Bounded instance
10
Заключение
Мы можем использовать newtype Monoid wrappers, чтобы сообщить компилятору, каким образом совместить вещи в парах.
Теги ничего не делают, но показывают, какую комбинацию использовать.
Это похоже на передачу функций в виде неявного параметра, а не явного (потому что такой тип класса классов вообще).