concatMap
Next we have
concatMap :: Foldable t => (a -> [b]) -> t a -> [b]
It takes a function f
that turns an a
into a list of b
s, f :: a -> [b]
,
and applies it to every element in the container. This gives us a container of
lists of b
s, which we can concatenate in the same way as concat
would do it.
Once again, this is simply the specialization of foldMap
where the monoid m
is the list monoid:
concatMap :: Foldable t => (a -> [b]) -> t a -> [b]
concatMap = foldMap
This may look familiar. Our earlier shenanigans to flatten a container into a list used the definition
toList = foldMap singleton
We could also define it as
toList = concatMap singleton
We convert each list element to a singleton list containing this element—that's the "map" part—and then we concatenate the resulting lists.
>>> concatMap singleton tree
[5,3,1,2]
A comment is in order. I illustrated the use of concat
in the previous section
by evaluating the expression concat $ fmap singleton tree
. First, we map
singleton
over the elements in the tree, turning each element into a singleton
list, and then we concatenate the resulting lists. concatMap
does the same.
More importantly, when discussing concatMap
as one of the functions available
for lists, I explained that at least logically if not in terms of actual
implementation, concatMap
is simply the composition of concat
and map
:
concatMap f = concat . map f
This works for lists. It also works for trees, only we have to use fmap
because map
works only for lists:
concatMap f = concat . fmap f
This raises the question whether this definition works for any foldable container. The answer is no, and the reason is a subtle one.
The definition works, as long as our container is a functor. We could have defined
concatMap :: (Functor t, Foldable t) => (a -> [b]) -> t a -> [b]
concatMap f = concat . fmap f
However, this definition would not work for any foldable container because if
you have a closer look at the Foldable
class, it does not define Foldable
as
a subclass of Functor
. Thus, we cannot rely on fmap
being available for
every foldable container.
I haven't come up with a natural example of a container type that is foldable
but not a functor. So I'm not entirely sure why Foldable
is not a subclass of
Functor
. In contrast, we discussed the Reader
functor as an example of a
container that is a functor but is not foldable.