Есть ли название для этой более высокоуровневой «би» версии дистрибутива в Haskell?
у меня естьBitraversable
называетсяt
который поддерживает эту операцию:
someName :: Monad m => (t (m a) (m b) -> c) -> m (t a b) -> c
Другими словами, можно взять функцию, которая принимает две монады, упакованные в побитовый обход, и превратить ее в отображение, которое принимает одну монаду, содержащую побитовый обход без слоя монад. Это что-то вроде побитовой и высокоуровневой версии; сигнатура типа похожа на эту:
\f -> \x -> f (distribute x)
:: (Distributive g, Functor f) => (g (f a) -> c) -> f (g a) -> c
Мои вопросы:
Есть ли стандартное имя для этой "высокоуровневой" версии
distribute
который работает с функциями, которые принимают дистрибутивы, а не сами дистрибутивы?Есть ли название для версии с возможностью обхода?
Работает ли он с каждым битрейсером/функтором/монадой/что угодно, или есть ограничения?
1 ответ
Согласно @Noughtmare, ваши функции «более высокого уровня» просто написаны в стиле передачи продолжения. Как правило, им не нужны дополнительные имена, потому что это правильные композиции функций:
highLevelDistribute = (. distribute)
Практически говоря, везде, где вы хотите вызвать аргумент:
highLevelDistribute f
это выражение эквивалентно:
f . distribute
и даже если вы используетеhighLevelDistribute
как первоклассная ценность, просто не так сложно написать и понять раздел(. distribute)
.
Обратите внимание, чтоtraverse
иsequenceA
немного отличаются, так как мы имеем:
sequenceA = traverse id
Вы могли бы возразить, что эта разница не требует отдельных имен, но это аргумент для другого дня.
Возвращаясь к , это версия CPS:
someOtherName :: m (t a b) -> t (m a) (m b)
который выглядит как бифункторный аналогdistribute
:
distribute :: (Distributive g, Functor f) => f (g a) -> g (f a)
Итак, я бы предложил изобрести , чтобы отразить это, иsomeOtherName
становится:
class Bifunctor g => Bidistributive g where
{-# MINIMAL bidistribute | bicollect #-}
bidistribute :: Functor f => f (g a b) -> g (f a) (f b)
bidistribute = bicollect id
bicollect :: Functor f => (a -> g b c) -> f a -> g (f b) (f c)
bicollect f = bidistribute . fmap f
Опять ваш "высший уровень"someName
просто правильная композиция:
someName = (. bidistribute)
Разумные законы для a, вероятно, включают следующее. Я не уверен, что они достаточно общие и/или исчерпывающие:
-- naturality
bimap (fmap f) (fmap g) . bidistribute = bidistribute . fmap (bimap f g)
-- identity
bidistribute . Identity = bimap Identity Identity
-- composition
bimap Compose Compose . bidistribute . fmap bidistribute = bidistribute . Compose
Что касается вашего вопроса № 3, не все s равны , по той же причине, что и не всеTraversable
с. A позволяет вам «раскрыть структуру» под произвольным функтором. Так, например, для списков нет экземпляра, потому что если бы он был, вы могли бы вызвать:
distribute :: IO [a] -> [IO a]
что позволит вам определить, был ли список, возвращаемый действием ввода-вывода, пустым или нет, без выполнения действия ввода-вывода.
Сходным образом,Either
являетсяBitraversable
, но этого не может бытьBidistributive
, потому что если бы это было так, вы могли бы использовать:
bidistribute :: IO (Either a b) -> Either (IO a) (IO b)
чтобы определить, вернуло ли действие ввода-выводаLeft
илиRight
без необходимости выполнять действие ввода-вывода.
Одна интересная вещь оbidistribute
что "другой функтор" может быть любым ; это не должно бытьApplicative
. Итак, как у нас:
sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a)
distribute :: (Distributive g, Functor f) => f (g a) -> g (f a)
у нас есть:
bisequence :: (Bitraversable t, Applicative f) => t (f a) (f b) -> f (t a b)
bidistribute :: (Bidistributive g, Functor f) => f (g a b) -> g (f a) (f b)
Интуитивно понятно, что секвенирование нуждается в силе аппликативного функтора.f
уметь «строить»f (t a)
из обхода его функториалаf a
"части", в то время как дистрибьюция должна принимать толькоf (g a)
отдельно. На практике это означает, что последовательность обычно выглядит так:
-- specialized to t ~ []
sequenceA :: [f a] -> f [a]
sequenceA (f:fs) = (:) <$> f <*> fs -- need applicative operations
в то время как распределение обычно выглядит так:
-- specialized to g ~ (->) r
distribute :: f (r -> a) -> (r -> f a)
distribute f r = fmap ($ r) f -- only need fmap
(Технически, согласно документации для Data.Distributive
,Distributive
класс требует толькоFunctor
а не какой-то коаппликативный класс из-за отсутствия нетривиальных сомоноидов в Haskell. См. этот ТАК ответ .)