cli  
Article Series

This article series discuss more than 30 different programming languages. Please read overview before you read any of the details.

Playing with Records Related Articles.

Where to Discuss?

Local Group

Preface

Goal: Exploring capability towards functional programming in Javascript.

Source Examples

You can obtain source examples here:

Javascript in Web development.

This blog is mainly talking about functional programming. For web development with javascript, you might desire to have a look at my other blog, for example this article series:

From Procedural to Functional

Functional programming capability in ecmacript is somehow limited. Ecmascript is never been intended to be a full blown functional language. And most of you doesn’t need pure functional anyway. Ecmascript is not Haskell. We all do code by how to do something, instead of what to do with it. We have loops, conditional and so on.

However, functional approach might improve your coding a lot. This article will show how you can get start functional programming, even with limited support from ecmascript. After years, Ecmascript support, for functional programming has getting better. These days we can push our code to be more like functional approach, rather than just imperative one.

Coding Style

For simplicity reason I separate functions into oneliner. And in the same time, using a word for variable name, instead of just x, y , a or b.

I believe, with this way, reader can understand easier.

Example in CLI

I also use CLI using nodejs. It is easier to get the result, compared with inspect element. And there is no need to use additional file such as html or css. Just the script, and here we go.


1: Imperative Example (Procedural)

Example Data

Before you get confused, consider examining this real situation. I use this real code for my eleventy (11ty) website. I have a bunch of articles, with title and tags, and I want to extract unique tags to be shown globaly, in a page called Archive by Tags.

To make the example simpler, I have made ready to use data structure. Instead of articles, I use my favorite songs. The data can be shown as below:

const songs  = [
  { title: "Cantaloupe Island",          tags: ["60s", "jazz"] },
  { title: "Let it Be",                  tags: ["60s", "rock"] },
  { title: "Knockin' on Heaven's Door",  tags: ["70s", "rock"] },
  { title: "Emotion",                    tags: ["70s", "pop"] },
  { title: "The River" }
];

Songs: Example Data

Preparing package.json

We are going to walk these code with nodejs. In order to import data as module, we have to set the type as module in package.json.

{
  "name": "songs-functional",
  "version": "1.0.0",
  "description": "",
  "dependencies": {},
  "author": "Epsiarto Rizqi Nurwijayadi",
  "license": "ISC",
  "type": "module"
}

Procedural Code

It is still working today.

In the old good ES6 (ecmascript 2015) days, a few years ago, we could write the code as below:

let tagSet = new Set();

// using set feature to collect tag names
songs.forEach(song => {
  if( "tags" in song ) {
    let tags = song.tags;
    console.log(tags);

    for (const tag of tags) {
      tagSet.add(tag);
    }
  }
});

console.log(tagSet);

// normalize to array
let allTags = [...tagSet];

console.log(allTags);

With the result similar to figure below:

$ node 21-songs-es6.js
[ '60s', 'jazz' ]
[ '60s', 'rock' ]
[ '70s', 'rock' ]
[ '70s', 'pop' ]
Set(5) { '60s', 'jazz', 'rock', '70s', 'pop' }
[ '60s', 'jazz', 'rock', '70s', 'pop' ]

Songs: Imperative Programming: Example Source Code

Functional Code

As a comparison, here is functional code using flatmap feature, from ES10 (Ecmascript 2019).

import songs from "./songs-data.js";

const unique = array => [... new Set(array)];

const allTags = unique(songs
  .filter(song => song.tags)
  .flatMap(song => song.tags)
);
console.log(allTags );

With the result similar to figure below:

$ node 14-songs-flatmap.js
[ '60s', 'jazz', 'rock', '70s', 'pop' ]

Songs: Functional Programming: Example Source Code

The thing is, writing functional code is easy. But the trade off is, this is not readable to beginner. Not because it is hard, but because they do not get used with the writing style.

Migration Issue

Swicthing Mind

The key to success is not about coding skills. But rather about switching mind from imperative thinking, to functional thinking.

You can spot mistakes, people doing functional programming, using imperative thinking in below 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));
const addSongTags  = (tagSet, song) => 
        song.tags? addTags(tagSet, song.tags) : undefined;
const addSongsTags = (tagSet, songs) =>
        songs.map(song => addSongTags(tagSet, song));
const 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);

The code is not just difficult to be understood. But most of all, the code is just, an imperative thinking in functional fashioned.


2: Custom Oneliner: Unique (Distinct)

The way javascript solving unique array has evolved from time to time.

Consider simple data as below:

const song = { title: "Cantaloupe Island", tags: ["60s", "jazz"] };

With ES 2015, we can solve unique array using Set. And with ES 2018, we can get the Set back to array easily, using spread operator as below code:

const tags = ["rock", "jazz", "rock", "pop", "pop"];

// using set feature to collect tag names
let tagSet = new Set();

for (const tag of tags) {
  tagSet.add(tag);
}

console.log(tagSet);

// normalize to array
const allTags = [...tagSet];

console.log(allTags);

With ES 2015, we can rewrite code above using fat arrow , also allowing array as direct parameter of Set class.

const unique = array => [... new Set(array)];
const tags = ["rock", "jazz", "rock", "pop", "pop"];

const allTags = unique(tags);
console.log(allTags);

With result as below:

$ node 03-tags-unique.js
[ 'rock', 'jazz', 'pop' ]

Songs: Functional Programming: Unique Tags

And you can also examine, how this custom Set class behave, by testing on empty tags:

const unique = array => [... new Set(array)];

const song1 = {
  title: "Cantaloupe Island", 
  tags: ["rock", "jazz", "rock", "pop", "pop"]
};

const song2 = { title: "The River" };

const song1Tags = unique(song1.tags);
console.log(song1Tags);

const song2Tags = unique(song2.tags);
console.log(song2Tags);

The result should be as below:

$ node 04-song-empty.js
[ 'rock', 'jazz', 'pop' ]
[]

3: Flat and Flatmap

I should write this style FP more often 🙂, in my project.

With ES 2019 flat and flatmap feature. There are new ways to extract tags from songs data. You may spot that all code has const, instead of let. This means all variablesd are immutable.

If you find out let cluase in your code, it means your code has side effect. Thus not truly following functional paradigm.

Using Flat

import songs from "./songs-data.js";

const
  unique   = array => [... new Set(array)],
  exist    = array => array.filter(a => a !== undefined),
  tagsOnly = songs => songs.map(song => song.tags);

const allTags  = unique(exist(tagsOnly(songs).flat())) ;
console.log(allTags);

With result as below:

$ node 11-songs-flat.js
[ '60s', 'jazz', 'rock', '70s', 'pop' ]

We make custom exist function, as additional helper function.

Using Flatmap (plain)

We can combine flat and map as just one flatmap function.

const
  unique   = array => [... new Set(array)],
  exist    = array => array.filter(a => a !== undefined);

const allTags  = unique(exist(songs.flatMap(song => song.tags)));

With about the same result as above.

Using Flatmap (with inner ternary)

const unique = array => [... new Set(array)];

const allTags = unique(songs.flatMap(song => song.tags? song.tags: []))

With about the same result as above.

If you don’t mind being cryptic you can write them all as oneliner.

import songs from "./songs-data.js";
console.log([... new Set(songs.flatMap(song => song.tags || []))]);

Just remember that this is not the best practice. Clarity first, or you might end up getting dizzy the other day.

Using Flatmap (chained with filter)

The syntax in example below is concise. Brief, but comprehensive.

const unique = array => [... new Set(array)];

const allTags = unique(songs
  .filter(song => song.tags)
  .flatMap(song => song.tags)
);

Still with about the same result as above.

Using Flatmap (chained with oldschool unique filter)

const allTags = songs
  .filter(song => song.tags)
  .flatMap(song => song.tags)
  .filter((v, i, a) => a.indexOf(v) == i)
;
console.log(allTags );

With exactly the same result as usual.

Songs: Using Flatmap: Chained with oldschool unique filter

I know the last line is cryptic. But it is the only chaining way I know. Using standard filter, without messing with array prototype, by making custom global chain property.

Unique Using Recursive Function

But why reinvent the wheel?

However we can solve unique elements with simple recursive function.

const unique = tags => {
  if (tags.length <= 1) {
    return tags
  } else {
    const [head, ...tail] = tags
    const newTails =
      unique(tail.filter(tag => tag != head))

    return [head, ...newTails]
  }
}

const tags =
  ["rock", "jazz", "rock", "pop", "pop"]

console.log(unique(tags))

With result as below:

$ node 31-tags-unique.js
[ 'rock', 'jazz', 'pop' ]

What is Next 🤔?

We are not finished yet. I will give an example of bad code.

Consider continue reading [ Ecmascript - Towards Functional - Part Two ].