Functional Programming - Part 3 - Letting Go

· 14 min read Đọc bài này bằng tiếng Việt

Style note: This series uses martial-arts/cultivation metaphors to tell the story of programming. This is an intentional choice by the author to make dry concepts more vivid.

Functional Programming is a different path, a different way of thinking in coding. At a more abstract level, Functional Programming is categorized as “Declarative,” while OOP falls under “Imperative.”

From grammar lessons, we know two sentence types: Declarative Sentences and Imperative Sentences.

Programming in the “Imperative” style means arranging a series of sequential commands for the computer to execute step by step. Here the focus is on “how.” Do this, then do that… A form of “hand-holding instruction.”

For example, a web page has 4 red boxes like this:

<style>
.box { width: 100px; height: 100px; float: left; margin: 10px; background-color: red; }
.hide { display: none; }
</style>
<div class="box hide"></div>
<div class="box hide"></div>
<div class="box hide"></div>
<div class="box hide"></div>

These boxes are hidden — we need to make them visible by removing the “hide” class.

The honorable computer science teachers of old used to teach writing it like this:

const els = document.querySelectorAll('.box');
for (let i = 0; i < els.length; i++) {
  let el = els[i];
  el.classList.remove('hide');
}

They use code to instruct the computer to perform each task.

Functional Programming cultivators don’t think that way.

No for/while

“Declarative Programming” focuses on “what.” We just need to define input and output rules. For instance, “if input is 1, output is 2.” The rest is for the computer to handle.

Functional Programming cultivators don’t need for loops.

Code like this looks much fresher:

const getElements = (selector) => {
  return Array.from(document.querySelectorAll(selector));
};

const getRemover = (el) => {
  return (className) => {
    el.classList.remove(className);
    return el;
  };
};

const els = getElements('.box')
  .map(getRemover)
  .map((removeClass) => removeClass('hide'));

console.log(els);

First we create a pure function getElements to retrieve elements on the page via CSS Selector. This collection is ArrayLike — we use Array.from to convert it to a real Array so we can leverage Array prototype methods.

Here we define input as CSS Selector, output as an array of DOM Elements.

getRemover is a higher-order function. We can call it by chaining getRemover(DOMElement)(classToRemove). By leveraging the characteristics of higher-order functions, after two maps, we reach the function returned by getRemover.

Here we define input as DOM Element, output as function() {takes className as input and returns the DOM Element without that class}.

With code like this, we can reuse the logic in many places — just change the input. For example, removing the float-left class from all div elements:

const els = getElements('div')
  .map(getRemover)
  .map((removeClass) => removeClass('float-left'));

No if/else

Functional Programming cultivators also don’t need if/else.

They’ve even created an entire Anti-IF campaign!

There are many ways to completely eliminate if/else from your program. The simplest is using ternary.

Ternary

In JavaScript, ternary — the Conditional Operator — is a concise way to write if/else.

Look at this verbose, complex branching code:

let title = 'Mr.';
if (person.gender === 'female') {
  if (!person.gotMarried) {
    title = 'Ms.';
  } else {
    title = 'Mrs.';
  }
}

It can be condensed to:

const title =
  person.gender === 'female' ? (!person.gotMarried ? 'Ms.' : 'Mrs.') : 'Mr.';

No more if/else.

We’ve also implicitly eliminated var and let because we no longer need to reassign title.

Logical operators

The second way is to exploit the hidden power of logical operators && and ||.

Suppose we have this code:

const sayHello = () => {
  console.log('Hello, bonjour, nihao');
  return true;
};

const doNothing = () => {
  console.log('Do nothing');
  return false;
};

const greet = (hasClient) => {
  if (hasClient) {
    sayHello();
  } else {
    doNothing();
  }
};

greet(true); // => 'Hello, bonjour, nihao'
greet(false); //=> 'Do nothing'

Logically, greet() checks: if there’s a client, greet them; otherwise, do nothing.

By the definitions of && and ||, we know:

  • expr1 && expr2 returns expr1 if expr1 is falsy; otherwise, it returns expr2.

An interesting thing here is that the JavaScript engine always evaluates logical expressions from left to right following the principle of “short-circuit evaluation.” Physics students might remember “short circuit” — when current doesn’t flow through the load or only flows through part of it.

Since AND only returns true if both operands are true, as soon as it encounters a false expr1, it immediately concludes the compound statement is False and stops there — never running expr2.

  • expr1 || expr2 returns expr1 if expr1 is truthy; otherwise, it returns expr2.

Since OR returns true if at least one operand is true, as soon as it encounters a true expr1, it immediately concludes the compound statement is True and skips expr2.

Short-circuit is divine!

Experienced developers often exploit this to optimize performance. They place complex computation expressions in the second half of a logical expression. This way, when the appropriate conditions aren’t met, they’re skipped — no resources wasted.

Now we can rewrite greet() in a cryptic way:

const greet = (hasClient) => {
  return (hasClient || doNothing()) && sayHello();
};

Start with the part in parentheses on the left of &&. If hasClient is truthy, this part’s value is hasClient, and doNothing() is skipped.

Since the left side of && is truthy, the expression’s final value reduces to the right side of &&sayHello().

The same reasoning applies when hasClient is falsy — the flow immediately branches to doNothing(). At this point, the left side of && is falsy, so sayHello() is irrelevant.

Writing this way is both unique and novel, eliminates if/else, and perfectly matches the conditional logic.

However, logical operators can be hard to follow if you’re not used to them. I’m only showing this for reference. In actual projects, stick with ternary to spare your teammates’ brains:

const greet = (hasClient) => {
  return hasClient ? sayHello() : doNothing();
};

Logical functions

Another approach expressing a more radical Functional Programming spirit is creating functions specifically responsible for logic handling. For example, Ramda.js and Sanctuary both have ifElse, unless, when, and dozens of other logic functions.

The greet function rewritten with Ramda becomes this lovely:

const R = require('ramda');

const greet = R.ifElse(R.identity, sayHello, doNothing);

That’s the artistic beauty of Function Composition. Just gaze at it and say nothing! Composition also means a creative work — like Paul Verlaine’s poetry or Beethoven’s music.

No new/this

There are two things that always make Brendan Eich satisfied when telling JavaScript’s history: first-class functions and the prototype mechanism.

Today, most developers know that inheritance in JavaScript is prototype-based. But in the web’s early days, people commonly used new and constructor functions to do OOP in JavaScript in a class-based style, similar to Java.

Classical inheritance

Ancient texts record many examples like this:

function Dog(name) {
  this.name = name;
  this.say = function () {
    console.log('woof-woof, my name is ' + this.name);
  };
}

var rocky = new Dog('Rocky');
rocky.say();

var molly = new Dog('Molly');
molly.say();

The Dog function is called a Function Constructor. Early Đại Việt cultivators translated it as “hàm dựng.”

Prototypal inheritance

At the start of the 3rd millennium, at the Yahoo! sect, a deeply cultivated elder named Douglas Crockford released the extraordinary book “JavaScript: The Good Parts,” emphasizing JavaScript’s prototype nature and the difference between classical and prototypal inheritance. He argued that the new keyword carries many drawbacks and encouraged using Object.create to copy prototypes to inheriting objects.

Douglas Crockford’s thinking was truly fresh. At the time, many JavaScript engines hadn’t yet supported Object.create. This book stirred up the entire programming world upon release, becoming the bedside book of countless cultivators.

Object.create allows copying an object’s properties or prototype. The Dog function can be rewritten in a prototypal inheritance style:

function Dog() {}

Dog.prototype.say = function () {
  console.log('woof-woof, my name is ' + this.name);
};

var rocky = Object.create(Dog.prototype);
rocky.name = 'Rocky';

var molly = Object.create(Dog.prototype);
molly.name = 'Molly';

rocky.say();
molly.say();

No more new!

Subsequent masters quickly developed many other prototypal inheritance approaches, most notably Concatenative inheritance, Prototype delegation, and Functional inheritance.

ES6 Class today only borrows classical OOP syntax as an interface — internally, it’s the prototypal inheritance mechanism.

Object Composition

But prototypal inheritance still belongs to OOP.

Functional Programming cultivators don’t need new.

Nearly 10 years after the bombshell “The Good Parts,” Douglas Crockford once again shook the programming world with “JavaScript: The Better Parts.” By this point, he no longer used Object.create(), and had also abandoned this, for loops, for in, while… His cultivation had taken another major step. In the talk, he discussed new ES6 features that hadn’t yet officially shipped. These grandmaster types are always years ahead of everyone else.

That was also when the Functional Programming trend was heating up again, and people began talking about Object Composition.

Paired with new is this. The this keyword is merely a trick to create an isolated context for executing functions. In JavaScript, each function is like an independent barrier. When a function is attached to an object, it’s called a method. The context in which that method runs is usually the object that owns it. Later came the techniques bind, apply, call to swap contexts.

For JavaScript newcomers, this sometimes becomes a terror. It’s very hard to debug issues in a function without knowing its exact execution context. And the context is usually unstable — or rather, it’s always mutable.

Functional Programming cultivators don’t need this.

The classical OOP code above can be rewritten as:

const sayName = (state) => {
  return Object.assign(state, {
    say: () => {
      console.log(`woof-woof, my name is ${state.name}`);
    },
  });
};

const createDog = (name) => {
  let state = { name };
  return Object.assign(state, sayName(state));
};

const rocky = createDog('Rocky');
rocky.say();
const molly = createDog('Molly');
molly.say();

Everywhere you look, you see functions.

No more for/while, if/else, new/this.

Are you ready to leave behind those mundane things?

Or as the monks say, can you let go?

Meditating monk — symbol of letting go

When traditional thinking has sunk deep into your mind, whenever you face an object hierarchy problem, you immediately think of OOP, class, prototype, inheritance… even treating them as the inevitable, only solution. Whenever you process collections, you loop. Whenever you see conditions, you rely on if… This is a huge obstacle for newcomers to the path.

You must find a way to shed what’s unnecessary, or you won’t go far.

Leave them behind, holding only one notion: FUNCTION!

One thought: “function”!

Bodhidharma — one thought: "function"

At first, it will naturally be difficult and awkward. Like how every day you walk the familiar road from home to office and back. Until one day that road is blocked by police, and you must take a detour.

On that unfamiliar road, you no longer see the landmarks you’ve seen every day: a souvenir shop, a gas station, a pawn shop, after the intersection comes the riverside, the red-painted bridge, a grocery store where a very pretty girl usually sits out front… You no longer encounter those familiar signs. You don’t know where you are, how many kilometers remain to home.

But any road becomes familiar after a few trips. Nothing to worry about. The point is, as soon as you recognize Functional Programming as something fascinating and worth learning and applying, you should start immediately — don’t wait for a convenient moment, don’t wait to find a master to guide you. If you do, you’ll struggle to leave the old ruts.

Krishnamurti once expressed something similar, roughly:

If you’ve been heading North all the days of your life, as humanity has followed a particular direction, and then someone appears and says, “That direction is not right.” Then they tell you, “Go South, East, any direction except that one.” And when you actually move away from that direction, there is a change right at the level of the brain cells because you’ve broken the pattern. And that pattern must be broken right now — not forty years or a hundred years from now.


Read more: If you want a more systematic and direct approach to FP, check out the series Becoming a Functional Programmer — a translation of Charles Scalfani’s famous series, also on this blog.

📎 Source: kipalog.com

Comments

  1. Loading comments…