Gabriella439 / pipes

Compositional pipelines

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Is "distribute" incorrectly named?

tomjaguarpaw opened this issue · comments

pipes has distribute with type

distribute
    :: (MFunctor t, ...)
    => Proxy a' a b' b (t m) r
    -> t (Proxy a' a b' b m) r

That is, we can push a Proxy in through an MFunctor. As far as I can tell this name was introduced by @Gabriel439 in a post in 2013, appealing to the concept of "distributive functor in the category of monads". I can't find any references to such a thing that I can understand. The best I've found is Linearly Distributive Functors which doesn't help much.

The name distribute has made its way into streaming and hedgehog where it retains the meaning of pushing something in through an MFunctor.

On the other hand we have a distributive package with a (*-level) distribute operation. It has type

(Distributive g, Functor f) => f (g a) -> g (f a) 

That is, we can pull any Distributive out through a Functor. This package does not provide any references for the name either.

At this point I'm wondering whether pipes got the name wrong at the outset. It's also possible that distributive got the name wrong. Without a reference to the original category theory terminology I can't tell. However, distributive as been around for a couple more years than distribute in pipes so it feels more likely to be correct. I have opened an issue on distributive for clarification.

@tomjaguarpaw: The names distribute/distributive come from "distributive law" in category theory

In Haskell terms, a the general notion of a distributive law is a function of type forall a . f (g a) -> g (f a) where f and g are usually Monads and this function has to obey certain rules.

The Distributive type-class from the distributive package is a special case of that general concept and the distribute function from pipes is another special case of that general concept, but neither descends from the other. In other words, they are siblings of each other.

I see. Is sequence :: (Traversable t, Applicative f) => t (f a) -> f (t a) also an example of a distributive law?

@tomjaguarpaw: So to satisfy all of the laws for a distributive law then technically both t and f have to also be Monads. I will actually replace f with s for consistency with the Wikipedia article.

In Haskell terms, if sequenceA were a distributive law and t and s were both Monads then the laws it would need to satisfy are:

-- using: sequenceA :: (Traversable t, Applicative s) => t (s a) -> s (t a)

join . fmap sequenceA . sequenceA = sequenceA . fmap join
    :: (Traversable t, Monad s) => t (s (s a)) -> s (t a)

fmap join . sequenceA . fmap sequenceA = sequenceA . join
    :: (Traversable t, Monad t, Applicative s) => t (t (s a)) -> s (t a)

pure = sequenceA . fmap pure
    :: (Traversable t, Applicative s) => t a -> s (t a)

fmap pure = sequenceA . pure
    :: (Traversable t, Applicative t, Applicative s) => s a -> s (t a)

Indeed, some of these laws match the laws for the Traversable class. For example, the third law above is a generalization of the following law for sequenceA:

sequenceA . fmap Identity = Identity

If you replace Identity with pure and swap both sides you get:

pure = sequenceA . fmap pure

Similarly, for this law for sequenceA:

    sequenceA . fmap Compose = Compose . fmap sequenceA . sequenceA

... if you swap both sides and replace Compose with join, you get:

join . fmap sequenceA . sequenceA = sequenceA . fmap join

... which is the first distributive law. I'm guessing with some effort you could relate the two laws more precisely.

I haven't actually sat down and checked if all Traversable instances must satisfy the distributive laws, but my hypothesis is that sequenceA is probably a distributive law for the cases where s and t and are both Monads.

Interesting. Thanks very much for the explanation. So it sounds like there are two different specific uses of this same general notion of "distributive" floating around.

@tomjaguarpaw: You're welcome! 🙂