Cover image by David on Flickr, cropped by me.
As a JavaScript developer you probably heard about smoosh-gate. Some TC39 people (who are responsible for designing the ECMAScript standard that defines the langauge behaviour of JavaScript) had the idea to rename flatten
to smoosh
and flatMap
to smooshMap
. The rational behind this being some websites who use MooTools would break if they don't. These methods are a proposal right now and not part of the standard.
Anyway, I don't know much about MooTools or standards, but I wanted to explain what these functions acutally do.
Why
Most of the time, when programming JavaScript, you are probably messing around with your most favorite functors, arrays and promises. Sometimes they are nested, but you don't care about this. You need an array of numbers and not an array of arrays of numbers or you need a JSON result from your server and not a promise of a promise of JSON result.
What
Promises already come with a then
method, that flattens out nested promises.
// loadDataA returns a promise
const dataAPromise = loadDataA();
// dataAPromise resolves to a result with an id that is used to load the next data
// loadDataB returns a promise too
const dataBPromise = dataAPromise.then(result => loadDataB(result.id))
// dataBPromise resolves to the resulting data
// and not the the promise returned from the callback above.
// It's flattened out right away so you only care about the result
const upperCasePromise = dataBPromise.then(result => result.someText.toUpperCase())
// upperCasePromise resolves to the upper case text
// returned by the previous callback.
upperCasePromise.then(upperCaseText => console.log(upperCaseText));
So there isn't much to do here. Some promise libraries like Bluebird come with seperate map
and flatMap
methods, but mostly you will use then
and don't care too much about flattening here.
The solution for this problem in arrays was to add a flatten
and flatMap
method to arrays. The flatten
method replaces every nested array in the array with the content of that nested element, it also removes empty elements.
This function could be written manually with the help of reduce
.
const flatten = a => a.reduce(
(newArray, element) =>
element instanceof Array
? [...newArray, ...element]
: element !== undefined? [...newArray, element] : newArray,
[]
);
const a = [1, [2, 3, 4], , 5];
flatten(a); // -> [1, 2, 3, 4, 5]
We reduce a
to a newArray
by adding every element
to it, if this array is an istanceof Array
we add every element of that element
to the newArray
. (The ...
-operator will create a new array for either case instead adding to the existing array, but I think you get the point).
The imperative version could look like that:
function flatten(a) {
let b = [];
for (let element of a) {
if (element instanceof Array) {
for (let subElement of element) {
b.push(subElement);
}
} else if (element !== undefined) {
b.push(element);
}
}
return b;
}
const a = [1, [2, 3, 4], , 5];
flatten(a); // -> [1, 2, 3, 4, 5]
The flatMap
version of this is simply calling flatten
on a new array that was emitted by a map
.
const flatMap = (f, a) => flatten(a.map(f));
const a = [1,0,7,-3];
flatMap(x => x != 0? 1/x : undefined, a);
// -> [1, 0.14285714285714285, -0.3333333333333333]
const c = [1,2,5];
flatMap(x => ''.padEnd(x, ".").split(""), c);
// -> [".", ".", ".", ".", ".", ".", ".", "."]
The real implementations will probably work like methods and not functions:
// Instead of
flatten(a.map(x => [1,x]));
// it would be
a.map(x => [1,x]).flatten();
// instead of
flatMap(x => [1,x], a);
// it would be
a.flatMap(x => [1,x]);
Conclusion
Flatten is a rather important action performed many times in every program, so it would be nice if JavaScript came with a built-in version, independent of its final name, lol.