Becoming a better JavaScript developer with Functional Programming

I’m only here now
Station Five
Published in
6 min readJan 22, 2019

There’s been a lot of buzz around functional programming over the last few years, and there’s a lot of sanity to be gained by jumping onboard, but only if you keep yourself in check.

What is Functional Programming?

I see Functional Programming (FP) as a philosophy. A set of principles that form an overarching approach. We’ll explore these principles below, in brief:

  • Abstracting procedures and logic to smaller deterministic, pure functions.
  • Maintaining immutability of data.
  • Passing functions as values. Since functions are on the same level as variables in javascript, they can be passed around and called elsewhere.
  • Composition and partial application of functions. This becomes almost a benefit from writing deterministically and immutably, and thus the style unfolds from here.

(See the end of the article for an explanation of terms, if you are unfamiliar.)

JavaScript and how FP helps

Variable mutability vs immutability

The problem:
Like many traditional languages, JavaScript variables can be mutated (even ones defined with const). This can seem like a freedom at first, but freedom quickly devolves into chaos.

Dealing with the possibility that variable values could be changed impacts not only the code you write, but the way you think while developing.

It demands wider cognitive focus, maintaining awareness of the entire application architecture, rather than simply focusing on the code within your scope.

The solution:
Introduce a ‘no mutation’ policy in your project.

This can be assisted by using an immutable data structure library such as ImmutableJS, ramda or mori, or simply deep-freezing native javascript objects and arrays.

We employ eslint rules to throw errors if code mutates variables (see eslint-plugin-immutable). This suits us great as it provides a first line of defence, and if one of the devs thinks a procedure would be accomplished more easily by mutation, it draws attention to this and we can discuss as a team as to whether it is safe and if there are benefits.

The immer library also provides an interesting angle to safely constrain mutation within a closure.

Lexical scoping and global variables

The problem:
Scope inheritance in javascript poses the risk of namespace collisions, and further exacerbates unexpected mutation of data (if there aren’t strict measures to prevent mutation across the project).

The solution:
Using a module system helps a lot with namespace collisions, but within a module file you still have to ensure your code plays nicely.

Wherever possible, break functionality into smaller, stand-alone, pure functions that only reference their own arguments.

Don’t get too hung up on this and start rejecting any PR that references variables outside of block scope, nor do you need to aim for every one of your module files to be 3 lines long.

Strike a balance and stay productive.

As a rule of thumb, if your files gets over 100 lines long, or you’re nesting functions that are more complex than a fat-arrow one-liner, try abstracting something to another function or file/component.

Working asynchronously / Promises

The problem:
Having to be aware of when a function call is going to return a Promise is not always easy; maybe it started out synchronous, and at some point, requirements added a need to make an asynchronous call. Now any code calling that function has to also change to accommodate the Promise.

The solution:
Promises are still a significant step towards functional programming, as they are monads (or monad-like, depending on who you ask), which are a data structure that carries metadata about its own state.

The async/await pattern is a breath of fresh air to avoid callbacks, however you’re still stuck returning a Promise, when only a portion of your return data might be asynchronous. It also still requires a catch block for rejections.

RxJS provides an alternative paradigm for having synchronous and asynchronous code written in a more consistent way, as well as a stash of tools to pimp your code.

We’re working on a major update to our in-house framework that will blur the line between Promises and monads, hopefully soothing some headaches and providing a consistent approach.

Dynamic typing

The problem:
Types in JavaScript are out of sight and out of mind, until you hit an Uncaught TypeError: somePrototypeMethod is not a function. Whoops, I thought that string had already been cast to a number…

Using false or null as an exception state is a hacky quick fix we’ve all made before, which saves time on the spot, but can introduce bugs down the line.

The solution:
Straight up, you need to be using TypeScript, Flow typed or an alternative typing system. If you think that sounds too hard to work with, it just proves that either you already don’t know what type your variables are, meaning you’re going to introduce bugs, or your variables can be one of many types, meaning you’re going to introduce bugs.

Using monads is a great way to keep track of exception states without fuzzy types or throwing errors.

Let’s write some functional code

Using a naive, front-end pseudo-framework, we’re going to render a list of users.

The old, imperative tangle:

Although it’s a small function, your eyes have to dart up and down to mentally execute the code and figure out what is in scope, and what values variables will have at the time of execution.

If you caught the hasAFriend undefined bug, then well done. Your eyes darted correctly.

Converted to a Functional + Typed approach:

The approach below is a realistic example of techniques we use, though usually on more complex problems.

We’re going to use currying, passing functions as values a Maybe monad and partial application (through calling a curried function),

Currying and partial application
On line 6, we curry our function renderUser, and on line 16 we call it to partially apply the configuration path to the user profile page. (This results in a function which takes (friendId: Maybe<string>, user: User) as arguments). Doing this avoids passing it through functionalRender just so renderUser has access it, but also means that renderUser is pure.

Passing functions as values
On line 19, we call _.get (another curried function) from lodash/fp with ['isFriend'] to create a new function, which we pass straight to users.includes to check if any users are friends.

Maybe monad
On line 24 we construct a Maybe monad, which will contain the value if it exists.
On lines 8–9 we map the monad value, and provide a fallback through orElse.

This should all be taken as an analogy for your broader architecture and more complex problems, where each piece of the puzzle is self-contained and a black box to other components. Functions are ignorant of the inner workings of other functions, and are pure and deterministic.

External dependencies like reading from config / data, and side-effectful actions like API calls can be injected in a thin layer above the meatier code, whether that is business or display logic. This opens up the possibility of code optimisation such as memoization, and makes testing much easier.

Lastly, just take it easy

Functional Programming needn’t be void of curly braces, and it shouldn’t be a badge of honour if your code is.

In our example, wended up with a longer file, introducing two libraries and required transpiling (for type-checking). These facts are valid arguments to keep things simple, however as your project scales to non-trivial size, increased structure does translate to simplicity and will gain you momentum.

FP should focus on:

  • Readability
  • Maintainability
  • Reducing bugs (and alongside this, improving debuggability)

Avoid the trap of playing code golf. If your FP code is too intricately trimmed and reduced to be able to refactor easily, you’re FP-ing wrong.

Terminology

A Deterministic function will always return the same result given the same arguments.

Pure — Function purity includes determinism, and also means there are no side-effects — nothing happens outside of the function (e.g. http requests, modifying html, global variables).

Immutability means not changing the value of existing variables.

Curried Functions are functions that accept only one argument and return a function which would otherwise be the second argument (etc). It allows for easy and immediate partial application.

Partial Application is ‘baking in’ or setting one of a function’s parameters, to create a new function which takes fewer arguments.

Composing functions means chaining them together to create one function that calls each of them in sequence, passing the return value of one to the next.

Monads are a container data structure, keeping track of a value, metadata about the value such as whether it represents a success or failure, and provides an interface to access the data in a consistent and safe way.

--

--