Есть ли название для этой более высокоуровневой «би» версии дистрибутива в 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

Мои вопросы:

  1. Есть ли стандартное имя для этой "высокоуровневой" версииdistributeкоторый работает с функциями, которые принимают дистрибутивы, а не сами дистрибутивы?

  2. Есть ли название для версии с возможностью обхода?

  3. Работает ли он с каждым битрейсером/функтором/монадой/что угодно, или есть ограничения?

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. См. этот ТАК ответ .)

Другие вопросы по тегам