flatMap
functionIf you, like me, have spent any time in MDN's Array reference, you've probably run across flatMap. It's description is rather impenetrable:
It is identical to a map() followed by a flat() of depth 1
For quite some time, I reacted to that description with head scratching and
general puzzlement. But relatively recently I began to understand what flatMap
is good for. My goal, dear reader, is to impart this knowledge to you.
The normal map
function lets us perform a 1-to-1 mapping. For each input
element we get back exactly one output element.
What flatMap
allows us to do is perform a 1-to-any mapping. For each input
element we can choose to:
In my experience, I mostly find flatMap
useful for its ability to return any
number of output elements for each input element. The ability to filter elements
at the same time is a nice bonus.
Let's move on to an example to show how flatMap
might be useful in an
almost-like-real-life sort of scenario.
Imagine we are working on a website for a bookshop, and we have the following data structures:
// typescript
interface Author {
name: string;
books: Book[];
}
interface Book {
title: string;
published: Date;
}
Suppose we want to write a function that takes a list of Author
objects and
returns a list of all of the Book
s. What are some ways we could write such a
function without flatMap
?
// typescript
const getAuthorsBooksIter = (authors: Author) => {
const books = [];
for (const author of authors) {
for (const book of author.books) {
books.push(book);
}
}
return books;
}
// Or a slightly more functional approach
const getAuthorsBooksFunc = (authors: Author) =>
authors.reduce(
(books, author) => books.concat(author.books)
);
Of course, you can write this with flatMap
too, and it's much shorter
(otherwise it would've made for a pretty lousy example):
// typescript
const getAuthorsBooksFlatMap = (authors) =>
authors.flatMap(author => author.books);
Another situation where flatMap
is very helpful is when writing recursive
functions. Say you want to write a function that takes in an object and produces
a list of the primitive values from that object.
// input
{
foo: 'bar',
baz: {
qux: 100,
zap: {
zorp: false
}
}
}
// output
['bar', 100, false]
We can write this function relatively easily with flatMap
:
const getObjValues = (o) => {
if (typeof o === 'object' && o !== null) {
return Object.values(o).flatMap((v) =>
getObjValues(v)
);
}
return [o];
}