I have a Bitraversable
called t
that supports this operation:
someName :: Monad m => (t (m a) (m b) -> c) -> m (t a b) -> c
In other words, it's possible to take a function that accepts two monads packaged into the bitraversable and turn it into a mapping that accepts a single monad containing a bitraversable without the monad layer. This is something like a bitraversable and higher-level version of distribute
; the type signature is similar to this:
\f -> \x -> f (distribute x)
:: (Distributive g, Functor f) => (g (f a) -> c) -> f (g a) -> c
My questions:
Is there a standard name for this "higher-level" version of
distribute
that works on functions that accept distributives rather than distributives themselves?Is there a name for the bitraversable version?
Does it work with every bitraversable/functor/monad/whatever, or are there restrictions?
As per @Noughtmare, your "higher level" functions
someName
anddistribute
are just written in continuation passing style. These generally aren't worth additional names, because they are just right function compositions:Practically speaking, anywhere you want to call
highLevelDistribute
on an argument:this expression is equivalent to:
and even if you're using
highLevelDistribute
as a first-class value, it's just not that hard to write and understand the section(. distribute)
.Note that
traverse
andsequenceA
are a little different, since we have:You could make an argument that this difference doesn't really warrant separate names either, but that's an argument for another day.
Getting back to
someName
, it's a CPS version of:which looks like a bifunctor analogue of
distribute
:So, I'd suggest inventing a
Bidistributive
to reflect this, andsomeOtherName
becomesbidistribute
:Again, your "higher level"
someName
is just right-composition:Reasonable laws for a
Bidistributive
would probably include the following. I'm not sure if these are sufficiently general and/or exhaustive:For your question #3, not all
Bitraversable
s areBidistributive
, for much the same reason that not allTraversable
s areDistributive
. ADistributive
allows you to "expose structure" under an arbitrary functor. So, for example, there's noDistributive
instance for lists, because if there was, you could call:which would allow you to determine if a list returned by an IO action was empty or not, without executing the IO action.
Similarly,
Either
isBitraversable
, but it can't beBidistributive
, because if it was, you'd be able to use:to determine if the IO action returned a
Left
orRight
without having to execute the IO action.One interesting thing about
bidistribute
is that the "other functor" can be anyFunctor
; it doesn't need to be anApplicative
. So, just as we have:we have:
Intuitively, sequencing needs the power of an applicative functor
f
to be able to "build" thef (t a)
from a traversal of its functorialf a
"parts", while distribution only needs to take thef (g a)
apart. In practical terms, this means that sequencing typically looks like this:while distribution typically looks like this:
(Technically, according to the documentation for
Data.Distributive
, theDistributive
class only requires aFunctor
rather than some coapplicative class because of the lack of non-trivial comonoids in Haskell. See this SO answer.)