Implementing concatMap
Implementing concatMap
The concat function joins a list of lists into a single
list:
concat [[1,2,3],[4,5,6]]
[1,2,3,4,5,6]
Prelude provides a function named concatMap that can be
naively implemented by composing concat and
map:
concatMap f = concat . map fTry implementing concatMap using
foldl and foldr. Which one is
better? Why?
Hints
Click to reveal
Remember that you can concatenate two lists using the (<>) operator:
λ [1,2,3] <> [4,5,6]
[1,2,3,4,5,6]Click to reveal
You can concatenate two lists by folding over them using the (<>) operator.
λ concatFoldl = foldl (<>) []
λ concatFoldl [[1,2],[3,4],[5,6]]
[1,2,3,4,5,6]Click to reveal
You can map over a list by applying your function and then adding the result to
a new list using (:)
λ mapFoldr f = foldr (\x acc -> f x : acc) []
λ mapFoldr (+1) [1..10]
[2,3,4,5,6,7,8,9,10,11]Solution
Click to reveal
We can write concatMap easily using either foldl or foldr. Let’s take a
look at a foldr based version first:
concatMapFoldr f = foldr (\x acc -> f x <> acc) []Like the other examples where we’ve used foldr, this version of concatMap is
lazy and well-behaved when we need to deal with infinite lists. The most obvious
example of this is directly passing an infinite list to concatMapFoldr:
λ take 10 $ concatMapFoldr (\x -> [x,x]) [1..]
[1,1,2,2,3,3,4,4,5,5]We can also handle cases where the input is a finite list, but our function
outputs infinite lists that we we want to concatenate together. You’ll notice in
this example we’re using a new function: repeat. This function takes a value
and generates a list of that value repeated indefinitely:
-- When we return an infinite list for each finite input element
λ take 10 $ concatMapFoldr (\x -> repeat x) [1,2,3]
[1,1,1,1,1,1,1,1,1,1]As you might expect, we can also handle both cases at the same time. Even with an infinitely long input, and a function that generates infinitely long lists from each input, we’re still able to get a finite result:
-- When we return an infinite list for each of infinite inputs
λ take 10 $ concatMapFoldr (\x -> repeat x) [1..]
[1,1,1,1,1,1,1,1,1,1]We can implement a foldl based version of concatMap just as easily as our
foldr version, we just need to flip around the order of some of our operations:
concatMapFoldl f = foldl (\acc x -> acc <> f x) []As you might expect, this version of concatMap doesn’t do well with infinite
lists. Whether you pass in an finitely list, generate one with the function you
pass in, or both, concatMapFoldl will hang and ever complete.
So, Which One Is Better?
In most cases, concatMapFoldr is the version of this function that we’d want.
If we look at the behavior of the concatMap defined for us in Prelude
you’ll notice it’s behavior with infinite lists matches our foldr based
implementation:
λ take 10 $ concatMap repeat [1..]
[1,1,1,1,1,1,1,1,1,1]