It does not matter what color or shape the box is, if it’s a functor, you can map on its content.

The M-word

In which we map, we concat, andThen we concatMap

Michel Belleville
Published in
6 min readAug 27, 2019

--

me> So… we’ve seen a lot of these monads you’ve been telling me about…

wat> Looks like someone is ready to generalize 😉

me> Frankly I’m not sure… 😅 but let’s go ahead and see where it leads us, my rubbery friend.

wat> Let’s. And to begin with, can you remind me what you know about functors?

me> They’re… 🥁 🥁 🥁 🥁 🥁 things that have a map function!

map : (a -> b) -> F a -> F b

wat> Close enough¹ 😃 every functor needs to implement a map function that has this signature (where F is that specific functors type). You can see a functor as box, and map as a way to extract what is inside, feed it to a function to make something else, and put this something else back into the same wrapping.

me> Yeah… but the box thing is kind of a metaphor, right?

wat> Right. Some functors act like boxes, others only if you stretch the definition of “box” a little… or a lot:

`Maybe` is a box that may or may not contain something
`Result` is a box that may contain a value you want, or an error
`List` is a box that may contain many values
`Array` is another multiple-values box
`Set` is a multiple-values box that contains no duplicates
`Dict` holds any values that can be accessed by their key
`Generator` is a box that promises to give something in the future
`Decoder` is also a future box, though it may hold a failure
`Cmd` a box that does stuff on the outside and returns with messages
`Sub` a box for the outside world to put messages in
`Html` a box shaped like your app page that comes back with messages
...

me> So, a functor is like something that wraps a value in a context, and map is a way to access that value while preserving this context. A Maybe that contains a value will still contain a (transformed) value, a Maybe that contains nothing keeps on containing nothing, a List keeps the same number of (transposed) elements, a Set will still have no duplicates, a Html will keep its nodes as you change the messages they may send, etc.

wat> Well done! Now to monads. What did we say about monads so far?

me> Well… to be a monad you need to also be a functor… so monads are also about wrapping stuff in a context… and while a functor needs to have a map function, a monad needs to implement an andThen function like:

andThen : (a -> M b) -> M a -> M b

wat> Precisely. So, while a functor allows you to transform a value wrapped in a context, preserving that context, a monad allows you to take the value in the context re-wrap it yourself, changint the context if you want. 🎁

me> Hmmm… so when the context is Maybe I can take the value in a Just and, depending on that value, either return another Just or a Nothing

wat> …when you have a Result you can take out the Ok value and return another Ok or an Err with the cause neatly folded inside…

me> …when I need to generate random value, and use said value to generate another, more complicated one, I can use andThen to take the future value and make a generator out of it that will make another future value…

wat> …and when you need to read a JSON string and match it to produce specific values (or fail when it doesn’t match), you can use andThen to take the string out and produce the next Decoder instance according to the value…

me> … and when I have a List… wait a minute… List does not have an andThen function! 😲 Is it not a monad?

wat> What? What do you mean it doesn’ t… 😨 Oh my duckness!

me> See? No andThen. 😶

wat> Wait a minute… *pokes around the library* What is this concatMap that suspiciously looks like an andThen… 😏

concatMap : (a -> List b) -> List a -> List b

me> 🤔 It does looks similar.

wat> That’s because it is actually another name by which andThen can go. One that you can read as a map-oriented description of what andThen does… provided you know what a concat does. Can you guess what it does?

me> I know what List.map does…

map : (a -> b) -> List a -> List b

wat> Good. Now let’s say you mistake map for andThen and feed it a transformation where the result type b is actually a List c ; what could the signature look like?

me> Well…

map : (a -> List c) -> List a -> List (List c)

wat> Now, this is not quite what concatMap (which is andThen for lists really) does, now is it? But suppose concat is supposed to correct this inelegant List (List c) we have once we’ve mapped…

me> Oooh, I think I’ve got it! 🎉

concat : List (List a) -> List a-- and now we can even write `concatMap`:
concatMap : (a -> List b) -> List a -> List b
concatMap transformation items =
items
|> map transformation -- gives us a `List (List b)`
|> concat -- flattens it to a `List b`
-- and we can event adopt a more point-free style and forget `items`
concatMap : (a -> List b) -> List a -> List b
concatMap transformation =
map transformation
>> concat

wat> And got it you have! Let’s now generalize a bit, because this definition works both for concatMap and andThen since those are the same function:

-- to be well-behaved monad, `M` you needs to be a well-behaved
-- functor, which mean it has a `map` function:
map : (a -> b) -> M a -> M b
-- it also needs something like a `andThen` (aka `concatMap`)
-- function that allows us to not only manipulate what is wrapped
-- (like `map` does) but also to manipulate the wrapping itself
andThen : (a -> M b) -> M a -> M b
-- `andThen` does the same as mapping over the elements with a
-- function that adds a layer of monadic wrapping, and then
-- un-wrapping it with something like `concat`
concat : M (M a) -> M a

me> Ok… but why is it concatMap for List and andThen for most other monads?

wat> This is an Elm thing. Elm’s designers thought long and hard about all this monadic thing and decided that, yes this is incredibly useful, but functional programming already presents quite the learning curve for a beginner without forcing them to learn all the most confusing underlying concept before getting started. Also, it doesn’t help the name provides no instinctual insight about what it is or what it does. And then, some people have made a joke of it, explaining monads in the most unhelpful (yet technically correct²) way:

Kids, don’t do this at home. I might be in the audience and kick your ass to the moon for it.

wat> So Elm creators decided it was not really necessary to know everything about monads to get things done with them, and came up with names for the monadic operator that are easier to get, depending on the context (andThen is not bad for something that is kind of another step inside a context, but it does sound less right when the context is “working with multiple values” ; on the other hand, concatMap reads a bit like “concat after map” which is more intuitive when you work on multiple values). So kudos them and their pragmatic spirit😃 👍

me> Still, this concatMap of yours, does it really do a concat after a map? 🤔

wat> Your optimization sens is tingling, isn’t it? Well, you’re right ; a map followed by a concat would traverse the list twice and would be sub-optimal. Good thing is, this is not what concatMap really does under the hood. But the result is the same, so you can think of it like this.

me> Well… I think I will need to not only think but also sleep on it.

wat> Have at it 😉

[1] there is more to functors than the map function, rules that we haven’t gone through so far, and may very well not go through in the course of these conversations. Should you be interested in them, I recommend the excellent Learn you a Haskell for great good which details all these intricacies, albeit for the Haskell language.

[2] if you have to say something is *technically* correct, there’s a good chance it’s also very unhelpful.

--

--