a -> m a
- это просто стрелка Клейсли с аргументами и типами результатов, равными a . Control.Monad. (> =>) составляет две стрелки Клейсли:
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
Подумайте flip (.)
, но для стрел Клейсли вместо функций.
Итак, мыможно разделить этот комбинатор на две части: композицию и «приложение»:
composeParts :: (Monad m) => [a -> m a] -> a -> m a
composeParts = foldr (>=>) return
mysteryCombinator :: (Monad m) => m a -> [a -> m a] -> m a
mysteryCombinator m fs = m >>= composeParts fs
Теперь (>=>)
и flip (.)
связаны в более глубоком смысле, чем просто аналогия;и стрелка функции (->)
, и тип данных, заключающий в себе стрелку Клейсли, Kleisli
, являются экземплярами Control.Category.Category .Поэтому, если бы мы импортировали этот модуль, мы могли бы на самом деле переписать composeParts
как:
composeParts :: (Category cat) => [cat a a] -> cat a a
composeParts = foldr (>>>) id
(>>>)
(определено в Control.Category) - это просто более хороший способ записи как flip (.)
.
Итак, я не знаю стандартного имени, это всего лишь обобщение составления списка функций.В стандартной библиотеке есть тип Endo a
, который содержит a -> a
и имеет экземпляр Monoid , где mempty
равно id
и mappend
равно (.)
;мы можем обобщить это для любой категории:
newtype Endo cat a = Endo { appEndo :: cat a a }
instance (Category cat) => Monoid (Endo cat a) where
mempty = Endo id
mappend (Endo f) (Endo g) = Endo (f . g)
Затем мы можем реализовать composeParts
как:
composeParts = appEndo . mconcat . map Endo . reverse
, что просто mconcat . reverse
с некоторой упаковкой.Однако мы можем избежать reverse
, который существует потому, что экземпляр использует (.)
вместо (>>>)
, используя Dual a
Monoid, который просто превращает моноид в моно с перевернутым mappend
:
composeParts :: (Category cat) => [cat a a] -> cat a a
composeParts = appEndo . getDual . mconcat . map (Dual . Endo)
Это показывает, что composeParts
является в некотором смысле «четко определенным шаблоном»: *