Preface
Goal: Continue Part One
4: Without Flat and Flatmap
So we have talked about edge cases using ES10 (ES 2019). What if we want to be more conventional, not using this brand new feature. Of course we can.
For simplicity, this example is using, spread operator from ES9 (ES 2018). You can manage not use this spread operator, and find oldschool alternatives from the internet.
Merge Unique
Avoid For-of
import songs from "./songs-data.js";
const
unique = array => [... new Set(array)],
merge = (array1, array2) => [...array1, ...array2],
mergeUnique = (a1, a2) => unique(merge(a1,a2));
// const mergeUnique = (a1, a2) => [... new Set([...a1, ...a2])];
let allTags = [];
songs.forEach(song => {
if(song.tags) {
allTags = mergeUnique(allTags, song.tags)
console.log(allTags);
}
});
As an alternative we can write it in cryptic way as below:
const mergeUnique = (a1, a2) => [... new Set([...a1, ...a2])];
It is still the long old code, with a little improvement. We are going to improve more.
Inline Ternary
Avoid Conditional
We can get rid of the conventional conditional checking, by using ternary.
import songs from "./songs-data.js";
const
unique = array => [... new Set(array)],
merge = (array1, array2) => [...array1, ...array2],
mergeUnique = (a1, a2) => a2 ? unique(merge(a1,a2)) : a1;
// const mergeUnique = (a1, a2) => a2 ? [... new Set([...a1, ...a2])] : a1;
let allTags = [];
songs.forEach(song => {
allTags = mergeUnique(allTags, song.tags)
console.log(allTags);
});
As an alternative we can write it more cryptic way as below code:
const mergeUnique = (a1, a2) => a2 ? [... new Set([...a1, ...a2])] : a1;
It is not recommended to write cryptic code.
Destructuring in Loop
Fancy Loop Syntax
We can rewrite the loop above as below
for (const song of songs) {
const { title, tags = [] } = song
allTags = unique(merge(allTags, tags));
console.log(allTags);
}
And even use destructuring syntax in loop.
import songs from "./songs-data.js";
const
unique = array => [... new Set(array)],
merge = (array1, array2) => [...array1, ...array2];
let allTags = [];
for (const { title, tags = [] } of songs) {
allTags = unique(merge(allTags, tags));
console.log(allTags);
}
This is actually simpler.
We can handle the default value for undefined
,
using { title, tags = [] }
, the empty array will be merged later.
Alternatively: Using Map
Avoid For-each
Finally we can get rid of the conventional loop, and using map iterator instead.
import songs from "./songs-data.js";
const
unique = array => [... new Set(array)],
merge = (array1, array2) => [...array1, ...array2],
mergeUnique = (a1, a2) => a2 ? unique(merge(a1,a2)) : a1,
mapUnique = (allTags, songs) => songs.map(song =>
allTags = mergeUnique(allTags, song.tags)
);
const allTags = mapUnique([], songs).pop();
console.log(allTags);
Nomore let
keyword. All are immutable.
With result as below output:
[ '60s', 'jazz', 'rock', '70s', 'pop' ]
If we do not use pop()
,
the code result will be as below:
$ node 07-songs-map.js
[
[ '60s', 'jazz' ],
[ '60s', 'jazz', 'rock' ],
[ '60s', 'jazz', 'rock', '70s' ],
[ '60s', 'jazz', 'rock', '70s', 'pop' ],
[ '60s', 'jazz', 'rock', '70s', 'pop' ]
]
Now you know how it works 😁. This is not a very intuitive approach.
Finally: Flatten Using Reduce
This is basically an oldschool method,
before flat
and flatmap
coming in.
import songs from "./songs-data.js";
const
unique = array => [... new Set(array)],
getTags = songs => songs.map(song => song.tags || []),
flatten = array => array.reduce((a,b) => a.concat(b));
const allTags = getTags(songs);
console.log(allTags);
We can handle the default value for undefined
,
using song.tags || []
, the reduce
will merge the empty array.
This way is more intuitive.
Opinion
It is a blessed that we have flat
and flatmap
,
that making it easier to solve this issue.
5: Not so Pure Functional
Using map without returning value is considered anti pattern.
We can somewhat, ask the map
function,
to iterate without really returning value.
This way, we can also avoid for-loop and stuff.
Just remember, this approach is using mutable
data,
that you can spot with the let
keyword.
Therefore, this is not a pure functional.
We are going to do it step by step, following original code with imperative thinking.
Map
Just a little improvement from the original code.
import songs from "./songs-data.js";
// using map without returning value is considered anti pattern.
const addTags = (tagSet, tags) => tags.map(tag => tagSet.add(tag));
let tagSet = new Set();
// using set feature to collect tag names
songs.forEach(song => {
if(song.tags) {
let tags = song.tags;
console.log(tags);
addTags(tagSet, song.tags);
}
});
console.log(tagSet);
// normalize to array
let allTags = [...tagSet];
console.log(allTags);
Ternary
And also removing conditional.
const
addTags = (tagSet, tags) => tags.map(tag => tagSet.add(tag)),
addSongTags = (tagSet, song) =>
song.tags? addTags(tagSet, song.tags) : undefined;
let tagSet = new Set();
// using set feature to collect tag names
songs.forEach(song => {
addSongTags(tagSet, song);
});
// normalize to array
let allTags = [...tagSet];
console.log(allTags);
More Map
I am getting dizzy.
const
addTags = (tagSet, tags) => tags.map(tag => tagSet.add(tag)),
addSongTags = (tagSet, song) =>
song.tags? addTags(tagSet, song.tags) : undefined,
addSongsTags = (tagSet, songs) =>
songs.map(song => addSongTags(tagSet, song));
// using set feature to collect tag names
let tagSet = new Set();
addSongsTags(tagSet, songs);
console.log(tagSet);
// normalize to array
let allTags = [...tagSet];
console.log(allTags);
Spread
And finally confused
const
addTags = (tagSet, tags) => tags.map(tag => tagSet.add(tag)),
addSongTags = (tagSet, song) =>
song.tags? addTags(tagSet, song.tags) : undefined,
addSongsTags = (tagSet, songs) =>
songs.map(song => addSongTags(tagSet, song)),
setToArray = tagSet => [...tagSet];
// using set feature to collect tag names
let tagSet = new Set();
addSongsTags(tagSet, songs); // no assignment
let allTags = setToArray(tagSet);
console.log(allTags);
With result as below output:
[ '60s', 'jazz', 'rock', '70s', 'pop' ]
Although this works well, this is siffcult to be understood. This is also actually, an imperative programming, written in functional fashioned.
Conclusion
Now you see the difference, between the three.
- The imperative programming.
- The functional programming.
- The imperative programming written in functional fashioned.
What do you think?
What is Next 🤔?
We still have interesting concurrency topic.
Consider continue reading [ Ecmascript - Concurrency with Event Emmitter ].