Ответ 1
a -> m a
- это просто стрелка Клейсли с аргументом и типами результатов, которые являются a. Control.Monad. ( >= > ) содержит две стрелки Kleisli:
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
Подумайте flip (.)
, но для стрелок Kleisli вместо функций.
Итак, мы можем разбить этот комбинатор на две части: состав и "приложение":
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, 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
является "четко определенным шаблоном" в некотором смысле:)