A picture of Serge

Functional programming on the Web (and why it matters)

Gone are the days when it was enough to add a few lines of JavaScript and, perhaps, some jQuery hacks here and there for your website to be considered interactive. The web has grown to become a very complex ecosystem, and so have the tools we use to develop it. At first, these were small libraries like Backbone.js, then came bigger frameworks such as AngularJS, which had a more declarative approach to handling DOM, followed by a number of other frameworks, notably React, which introduced an even more structured and modularised way of handling data and communication. But no amount of frameworks, libraries, and tools could fix one huge problem – JavaScript. Whether you love the language or not, I believe, everyone who used it for quite some time will agree that JavaScript is flawed in many ways: weird and counterintuitive type coercion, complicated scoping rules, hoisting, virtually no type system; the list could go on and on.
And so we started evolving the language. Recent changes to the ECMAScript specification have completely transformed JS, which introduced a somewhat generally accepted set of best practices; along with that, a couple of extensions introducing static typing became available, with TypeScript being the most successful. But what if I told you that most of these features were heavily inspired by what many functional programming languages have had for many years? Let us take a closer look at what this really means and where we might apply it!

So, what is functional programming really?

The most general definition would be that it is a style of programming that favours expressions over statements. The latter is what students usually learn at their CS101 – programming is giving instructions to a computer on how to perform a certain task. Assign x to this, return that, iterate for this much. A well-known metaphor compares programs to cooking recipes, as both are sequences of concrete instructions which, if executed precisely, deliver an expected result. Expressions, on the other hand, are more like very specific descriptions of what the expected result is. Compare these two code snippets, the first one is in JavaScript and the second is in Haskell:

const xs = []
for (let i = 1; i <= 10; i++) {
  if (i % 2 === 0) {
    xs.push(i)
  }
}

(of course it can be optimised, but that is not the point here)

xs = [i | i <- [1..10], even i]

Both programs create a list of even integers up to ten. The first one, however, gives very specific instructions about how to construct the list, while the second just describes it: “xs is a list of i, where i is an even number from 1 to 10”. Much more concise, readable, and semantic! Also, notice how the JS program has a notion of a state: with every second iteration of the loop, the array gets updated, while in the Haskell example, there is no intermediary step. Moreover, we are not even allowed to update the array in Haskell, as all data is immutable.
To wrap it up, functional style of programming is one in which programs are made of composable stateless expressions which operate on immutable data. Just to mention, there are other features prevalent in functional programming languages, such as higher-order functions, expressive type system, partial decomposition of functions, and many more; we will touch some of these later in the article.

Where, why, and how to use functional programming?

You’re probably wondering: “But how is it related to web development and JavaScript? I will not write web apps in Haskell, will I?”. Well, it is possible, and I will come to this topic later, but, actually, you don’t have to, as most of mainstream languages, JavaScript in particular, support (and even encourage) such a style of programming. Hopefully this answers the ‘where’ – everywhere you can!
Onto the ‘why’ – because it’s an inherently more controlled style of programming. As your apps become ever complex, you don’t lose track of how different parts affect one another. This is especially true with the current state of highly concurrent web applications that might do a billion things at the same time while trying to provide consistent and fluent UI at the same time. Even though no sufficiently powerful tool, language, or programming paradigm can fully ensure your code is bug-free, it is hard not to notice how less of a burden runtime exceptions and weird bugs become once you start thinking more functionally. So, how to apply this magical paradigm to your frontend code? Let us take a deeper look on FP in JS!

JavaScript as a functional programming language

Turns out, JavaScript provides a lot of the functional tools and features out of the box, so you can easily start incorporating them in your code, although I will also briefly mention where an extra library would be helpful.

Functions as first-class citizens

As implied from the name, functional programming is to a great extent about functions, and without a doubt JavaScript does embrace them! You can pass functions as arguments (what’s usually referred to as a ‘callback’), return functions, assign functions to variables, create anonymous functions, and many more – as a bonus, ES6 introduced arrow functions, which considerably simplifies the use of the aforementioned features. It allows writing smaller, more generic, versatile, and highly composable functions, which comes hand in hand with the DRY principle, keeping your code easily testable and maintainable. Let us take a look at how we can rewrite our little example from before using higher-order functions.

// create array of integers from min to max
const range = (min, max) =>
  (min > max ? [] : [min].concat(range(min + 1, max)))

const filter = pred => arr => arr.filter(pred)

const filterEven = filter(x => x % 2 === 0)

const evens = filterEven(range(1, 10)) // [ 2, 4, 6, 8, 10 ]

Take a closer look at filter: essentially, we are just recomposing the arguments of the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter">Array.prototype.filter</a> method so that our function takes the predicate and returns another function, which expects an array and only then performs filtering and returns the result. This allows us to write generic filtering functions by composing our filter with any predicate (like in filterEven).

Immutability and purity

These are the two constraints enforced by most of functional programming languages. A variable is called immutable if the data it is referring to and the reference itself cannot be altered in any way within its scope (essentially making it a constant). On the other hand, a function is pure if it has no state, so the only thing which influences the output of a pure function is its arguments – no matter when and where you call such a function, it will always produce the same result, there is no need to track dependencies in your code.
Although it might seem limiting, both constraints help us be more confident that our programs do not have unwanted side-effects and will not misbehave during runtime, which makes them almost a must-have when any concurrency or asynchrony is involved.
So, how to use it in JavaScript? Well, it gets a little bit complicated. While ES6 introduced the const keyword, it only ensures one cannot reassign a constant on another value, and does not ensure immutability of the data to which the constant is assigned. But it does not mean you have to do this! It is completely up to a developer to decide whether to follow these constraints or not. Of course, it might be quite hard to make everything pure and immutable – after all, we want our functions to have side-effects sometimes, otherwise, our programs will not do anything useful, but what I would suggest is to try to stick with immutability and purity as much as possible, at least in the business logic layer, and when it comes to handling application state, to keep it in one place (fortunately, modern frameworks provide an easy way of accomplishing this with libraries such as Redux and Vuex).

Compare this rather typical way of operating on objects:

class User {
  constructor(age, name) {
    this.age = age
    this.name = name
  }

  getAge() { return this.age }

  growOlder() { this.age += 1 }
}

const user = new User(33, 'Freya')
user.getAge() // 33
user.growOlder()
user // User { age: 34, 'Freya' }

With a more functional approach:

const User = (age, name) => Object.freeze({ age, name })

const getAge = usr => usr.age

const growOlder = ({ age, name }) => User(age + 1, name)

const user = User(33, 'Freya')
getAge(user) // 33
const olderUser = growOlder(user)
user // { age: 33, 'Freya' }
olderUser // { age: 34, 'Freya' }

Firstly, we see a strict separation between data and functions. While with classes we are basically forced to use this inside methods, the second implementation has only functions that operate exclusively on their arguments. That being said, none of the arguments are being modified in the second example – this is additionally enforced by using <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze">Object.freeze</a>, a function which makes a passed object immutable.

You might be tempting to say that this is memory-inefficient, and it is true: copying entire objects every time you want to increment a property sounds like a bad idea, but with the right tools immutability can actually be a big memory saver. One of such tools is Immutable.js, which is a library from Facebook that, in addition to enforcing immutability, also provides structural sharing, which allows for sharing common data between different objects – in the case of our User this would mean that the object returned by growOlder would not copy the name property, but instead refer to the one already stored in the user object.

Types!

Although it is not an inherently functional feature, types greatly improve predictability and control of your code, and this is the core of functional programming, so most FP languages have a very powerful and expressive static type system. Sadly, JavaScript does not provide any of this, which is why I strongly recommend TypeScript or Flow, both of which add static type-checking to the language. If you have never used either of them, you will quickly notice how many bugs can be eliminated with types during compile time. I would also briefly mention the fact that many FP languages do not have a concept of null, which is indeed useless when both static types and immutability are enforced. With that in mind, I recommend you to try to avoid nulls in your code, especially given that even the creator of a null himself calls it “the billion dollar mistake”.

Declarative Programming

The last tip is just to think more in terms of expressions rather than imperative statements. This goes really well together with all the features mentioned above. For instance, array methods like <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map">map</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter">filter</a>, and <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce">reduce</a> help you transform arrays and chain these transformations in a very declarative and concise way (you will not need for loops anymore 🙂 ). Another useful feature from ES6 is Promises, which allow us to abstract asynchronous operations, easily compose & chain them together, and handle errors in a very controlled way.

That being said, JavaScript still lacks a lot of features a typical FP language has, so in case you want to go really functional, I would recommend checking out a library called Ramda, which comes with currying, point-free function chaining, and a lot of other features from the world of functional programming. For example, this is how our filtering even numbers from 1 to 10 would look using Ramda:

const R = require('ramda') // ES6: import * as R from ‘ramda’

const filterEven = R.filter(x => x % 2 === 0)

const evens = filterEven(R.range(1, 11)) // [ 2, 4, 6, 8, 10 ]

As you can see, a lot of the stuff we had to implement manually is covered by the library, which makes functional programming is JS a breeze 🙂

As a side note, I want to stress once again that functional paradigm in a more general sense is just a set of best practices and a particular approach to reasoning about code, and thus, does not require dozens of libraries for a programmer to reap the benefits. In fact, having a lot of constraints from these libraries and not approaching programming functionally can be more detrimental and frustrating than beneficial.

Functional alternatives of JavaScript

Up to this point, I was mostly focusing on JavaScript, since it is where most of the readers will be able to apply the concepts in practice. Nevertheless, I must mention that there are other functional languages that compile to JS and thus can be used on the Web: ClojureScript , Reason, PureScript, Scala.js, to name a few. There is one, however, that is worth writing about a little more – Elm. “A delightful language for reliable webapps”, as the official website calls it, is also a framework in and of itself, and it comes with all the tooling you need for the development: a compiler, package manager, development server, builder, besides it has support for syntax highlighting, intellisense, and auto linting/formatting for every popular code editor! What really separates Elm from the other languages I mentioned is that it takes pragmatism of JavaScript and combines it with safety, reliability and expressiveness of a functional language, while also focusing on simplicity and ease of use, which makes it a great language to start your journey into functional programming.

Whether you plan to try out a functional language, or to incorporate new concepts and libraries to your JavaScript toolbox, or you would rather like to stick to your usual way of Web development, I hope you have gained some useful information from this article, and it will help you write safer, more predictable code with less bugs and more joy ^_^

FacebookTwitterPinterest

Serge Korzh

Data Scientist

I believe data is the key to solving many of the world’s biggest problems. My journey into Data Science started from Natural Language Processing, which, in turn, grew from my passion for linguistics. Currently my interest lies in applications of Machine Learning in health and development of sustainable solutions.