Dysfunctional Programming in JavaScript

Rajeev K
6 min readSep 18, 2020

--

Dysfunctional Programming in JavaScript
Photo by Sean Lim on Unsplash

A lot of JavaScript programmers seem to think that functional programming is simply writing bare functions without classes. But that’s procedural programming, not functional programming. So what is functional programming then?

What is functional programming?

Ask a math professor what factorial is, and he is likely to come up with a definition similar to this definition from a Math textbook:

It turns out this is very similar to how you implement factorial in Haskell, and that’s not a coincidence:

factorial 0 = 1 
factorial n = n * factorial (n - 1)

In functional programming, you define what things are — like your math professor would — as opposed to writing step-by-step instructions on how to calculate things. The latter is imperative programming.

Here’s how a JavaScript programmer might implement factorial:

function factorial(n) {
let x = 1;
while (n > 0) {
x = x * n;
n = n — 1;
}
return x;
}

This is a function but it is not functional because it mutates state and uses iteration.

The functional way

To be functional it is not sufficient to simply remove classes and write top-level functions. You have to completely change your way of thinking. The functional way has no side effects or state mutations. It has no assignments, no iteration, and no conditional statements.

Assignment and iteration are typical of imperative programming. In functional programming changing the value of a variable using an assignment statement is not allowed. Recursion takes the place of iteration, and conditional expressions are favored over conditional statements.

Limitations of JavaScript

Limitations of JavaScript become obvious when you try to follow the rules of functional programming in JavaScript.

First, let’s rewrite the factorial function above using recursion. After all, JavaScript supports recursion too, right?

function factorial(n, product = 1) {
if (n < 2) {
return product;
}
return factorial(n - 1, n * product);
}

Unfortunately, this doesn’t work very well in JavaScript because it lacks a critical feature known as tail call optimization (TCO). This feature prevents the stack from growing in proportion to the number of levels of recursion. Chrome has decided not to implement this feature. Lack of TCO means you can’t avoid iteration in JavaScript.

But that’s not all. Let’s look at some features that make functional programming awesome.

Here’s how you calculate the sum of the first 10 prime numbers using Rust programming language (assumes existence of is_prime function):

let sum = (0..).into_iter().filter(|&x| is_prime(x)).take(10).sum();

You can do something that looks similar (if a bit longer) in JavaScript too:

const sum = [...Array(100)].map((_, i) => i).filter(n => is_prime(n)).slice(0, 10).reduce((a, b) => a + b, 0);

Unfortunately, it only looks similar. Appearances can be deceptive. It doesn’t work similar at all. This is because JavaScript lacks a critical feature known as lazy evaluation. Lazy evaluation means not calculating things until they are actually needed.

In the JavaScript version, an array of 100 elements is created whether you use all 100 elements or not. In the Rust version an infinite range (0..) is specified but thanks to lazy evaluation it doesn’t actually try to create an infinite range because you are not using all of those values. In the Rust version calculations are only done when the final consumer of the iteration actually tries to use the result of the calculation.

Because of lack of lazy evaluation in JavaScript, code that looks cool — like in functional languages — is actually quite inefficient when compared to functional languages.

There are even more cool functional features missing in JavaScript, such as pattern matching.

Because of a lack of features such as tail call optimization and lazy evaluation you can’t do “real” functional programming in JavaScript.

Functional programming and side effects

In functional programming, a function is supposed to not have any side effects. It is not supposed to modify any global state, it is not supposed to print anything to the console and it is certainly not supposed to mutate the DOM.

If a function can’t change anything in the world, and your program is built of such functions, then how is it supposed to do anything useful? At some point you’re going to need side-effects such as mutating the DOM, right?

It turns out that FP languages such as Haskell have a system for dealing with functions that have side-effects, that neatly separates the part of the program that is pure and the part of the program that is impure (and does all the dirty work like updating the screen). Impure parts of the program is fenced off from the pure parts. Pure functions cannot call impure functions.

With pure and impure parts separated, we can still reason about our mostly pure program (which is the majority of our code) and take advantage of all the things that purity offers while still communicating with the outside world.

The important part here is keeping portions of your code that has side-effects separate — and minimizing the amount of such code. If code that has side-effects is spread all over your program then you are not really using functional style.

When to use functional programming

Just because you can’t do pure functional programming in JavaScript doesn’t mean you shouldn’t write functional-style code where it makes sense. If you are a JavaScript programmer, whether you realize it or not, you may already be using features that originally came from functional programming languages, such as lambdas and closures.

Modern programming languages such as Rust are multi-paradigm for a reason. There is no reason to eschew one paradigm in favor of another. Among some JavaScript programmers, it has become fashionable to completely avoid classes and OOP because they think functional is better. But given that you can’t do real functional programming in JavaScript, completely avoiding OOP will only eliminate the advantages of OOP while not gaining the benefits of pure functional style.

The slumber and awakening of functional programming

Functional programming is not new. It is almost as old as programming itself. The first functional programming language, LISP, was developed in the late 1950s. Functional programming has been available for many decades but it didn’t garner much interest from the mainstream until recently. It is important to understand the reason for its resurgence before abandoning all other paradigms in favor of functional.

Moore’s law is coming to an end, and CPUs are adding more cores instead of making cores faster and faster as they have done in the past. Taking advantage of multiple cores is hard.

To take advantage of the extra cores in a CPU you add threads, but if these threads are continually needing to lock in order to serialize access to shared state then concurrency is diminished. The more cores you have, the more threads you have, and the more the contention for access to shared state.

What if you used a different style of programming that doesn’t use shared state? Then you wouldn’t have to lock so much. The actor model is one such style and functional programming, because it eschews mutable state, is another. This makes it easier to take advantage of multicore processors and that is the reason for the renewed interest in functional programming.

But when it comes to JavaScript these benefits don’t exist because JavaScript code doesn’t use multiple threads (except for web workers, but web workers don’t share mutable state with the main thread). That’s not to say there are no benefits at all to incorporating some functional style into JavaScript code, but wholesale replacement of OOP with functional style is not warranted.

--

--