Consider the following signature of foldMap
foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
This is very similar to "bind", just with the arguments swapped:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
It seems to me that there therefore must be some sort of relationship between Foldable
, Monoid
and Monad
, but I can't find it in the superclasses. Presumably I can transform one or two of these into the other but I'm not sure how.
Could that relationship be detailed?
Monoid
andMonad
Wow, this is actually one of the rare times we can use the quote:
Let's start with a monoid. A monoid in the category
Set
of sets is a set of elementsm
with an empty elementmempty
and an associative functionmappend
to combine elements such thatNote that a monoid is not limited to sets, there also exist monoids in the category
Cat
of categories (monads) and so on. Basically anytime you have an associative binary operation and an identity for it.Now a monad, which is a "monoid in the category of endofunctors" has following properties:
It's an endofunctor, that means it has type
* -> *
in the CategoryHask
of Haskell types.Now, to go further you must know a little bit of category theory I will try to explain here: Given two functors
F
andG
, there exists a natural transformation fromF
toG
iff there exists a functionα
such that everyF a
can be mapped to aG a
.α
can be many-to-one, but it has to map every element ofF a
. Roughly said, a natural transformation is a function between functors.Now in category theory, there can be many functors between two categories. Ina simplified view it can be said that we don't even care about which functors map from where to where, we only care about the natural transformations between them.
Coming back to monad, we can now see that a "monoid in the category of endofunctors" must posess two natural transformations. Let's call our monad endofunctor
M
:A natural transformation from the identity (endo)functor to the monad:
And a natural transformation from the conposition of two monads and produce a third one:
Since
×
is the composition of functors, we can (roughly speaking) also write:Satisfying these laws:
So, a monad is a special case of a monoid in the category of endofunctors. You can't write a monoid instance for monad in normal Haskell, since Haskell's notion of composition is too weak (I think; This is because functions are restricted to
Hask
and it's weaker thanCat
). See this for more information.What about
Foldable
?Now as for
Foldable
: there exist definitions offold
s using a custom binary function to combine the elements. Now you could of course supply any function to combine elements, or you could use an existing concept of combining elements, the monoid. Again, please note that this monoid restricted to the set monoid, not the catorical definition of monoid.Since the monoid's
mappend
is associative,foldl
andfoldr
yield the same result, which is why the folding of monoids can be reduced tofold :: Monoid m, Foldable t => t m -> m
. This is an obvious connection between monoid and foldable.@danidiaz already pointed out the connection between
Applicative
,Monoid
andFoldable
using theConst
functorConst a b = Const a
, whose applicative instance requires the first parameter ofConst
to be a monoid (nopure
withoutmempty
(disregardingundefined
)).Comparing monad and foldable is a bit of a stretch in my opinion, since monad is more powerful than foldable in the sense that foldable can only accumulate a list's values according to a mapping function, but the monad bind can structurally alter the context (
a -> m b
).Summary:
(>>=)
andtraverse
look similar because they both are arrow mappings of functors, whilefoldMap
is (almost) a specialisedtraverse
.Before we begin, there is one bit of terminology to explain. Consider
fmap
:A Haskell
Functor
is a functor from the Hask category (the category with Haskell functions as arrows) to itself. In category theory terms, we say that the (specialised)fmap
is the arrow mapping of this functor, as it is the part of the functor that takes arrows to arrows. (For the sake of completeness: a functor consists of an arrow mapping plus an object mapping. In this case, the objects are Haskell types, and so the object mapping takes types to types -- more specifically, the object mapping of aFunctor
is its type constructor.)We will also want to keep in mind the category and functor laws:
In what follows, we will work with categories other than Hask, and functors which are not
Functor
s. In such cases, we will replaceid
and(.)
by the appropriate identity and composition,fmap
by the appropriate arrow mapping and, in one case,=
by an appropriate equality of arrows.(=<<)
To begin with the more familiar part of the answer, for a given monad
m
thea -> m b
functions (also known as Kleisli arrows) form a category (the Kleisli category ofm
), withreturn
as identity and(<=<)
as composition. The three category laws, in this case, are just the monad laws:Now, your asked about flipped bind:
It turns out that
(=<<)
is the arrow mapping of a functor from the Kleisli category ofm
to Hask. The functor laws applied to(=<<)
amount to two of the monad laws:traverse
Next, we need a detour through
Traversable
(a sketch of a proof of the results in this section is provided at the end of the answer). First, we note that thea -> f b
functions for all applicative functorsf
taken at once (as opposed to one at each time, as when specifying a Kleisli category) form a category, withIdentity
as identity andCompose . fmap g . f
as composition. For that to work, we also have to adopt a more relaxed equality of arrows, which ignores theIdentity
andCompose
boilerplate (which is only necessary because I am writing this in pseudo-Haskell, as opposed to proper mathematical notation). More precisely, we will consider that that any two functions that can be interconverted using any composition of theIdentity
andCompose
isomorphisms as equal arrows (or, in other words, we will not distinguish betweena
andIdentity a
, nor betweenf (g a)
andCompose f g a
).Let's call that category the "traversable category" (as I cannot think of a better name right now). In concrete Haskell terms, an arrow in this category is a function which adds an extra layer of
Applicative
context "below" any previous existing layers. Now, considertraverse
:Given a choice of traversable container,
traverse
is the arrow mapping of a functor from the "traversable category" to itself. The functor laws for it amount to the traversable laws.In short, both
(=<<)
andtraverse
are analogues offmap
for functors involving categories other than Hask, and so it is not surprising that their types are a bit similar to each other.foldMap
We still have to explain what all of that has to do with
foldMap
. The answer is thatfoldMap
can be recovered fromtraverse
(cf. danidiaz's answer -- it usestraverse_
, but as the applicative functor isConst m
the result is essentially the same):Thanks to the
const
/getConst
isomorphism, this is clearly equivalent to:Which is just
traverse
specialised to theMonoid m => Const m
applicative functors. Even thoughTraversable
is notFoldable
andfoldMapDefault
is notfoldMap
, this provides a decent justification for why the type offoldMap
resembles that oftraverse
and, transitively, that of(=<<)
.As a final observation, note that the arrows of the "traversable category" with applicative functor
Const m
for someMonoid
m
do not form a subcategory, as there is no identity unlessIdentity
is among the possible choices of applicative functor. That probably means there is nothing else of interest to say aboutfoldMap
from the perspective of this answer. The only single choice of applicative functor that gives a subcategory isIdentity
, which is not at all surprising, given how a traversal withIdentity
amounts tofmap
on the container.Appendix
Here is a rough sketch of the derivation of the
traverse
result, yanked from my notes from several months ago with minimal editing.~
means "equal up to (some relevant) isomorphism".When a container is
Foldable
, there is a relationship betweenfoldMap
andApplicative
(which is a superclass ofMonad
).Foldable
has a function calledtraverse_
, with signature:One possible
Applicative
isConstant
. To be an Applicative, it requires the "accumulator" parameter to be aMonoid
:for example:
We can define
foldMap
in terms oftraverse_
andConstant
this way:We use
traverse_
to go through the container, accumulating values withConstant
, and then we usegetConstant
to get rid of the newtype.