Micro Focus is now part of OpenText. Learn more >

You are here

You are here

Top functional tools for JavaScript programmers

public://webform/writeforus/profile-pictures/headshot_4.jpg
Charles Scalfani CTO, Panoramic Software
Code on a laptop
 

The demands on front-end developers have increased exponentially over the last decade to the point where JavaScript codebases are becoming more and more complex. As a piece of software becomes more complex, it becomes more difficult to reason about it.

We don't know how things work because there are too many interactions with other parts of the system. And those parts interact with others and so on and so forth. Unfortunately, there are no silver bullets for JavaScript developers. However, functional programming has many attributes that, when used correctly, can reduce complexity, and drastically limit the side-effects often created in large codebases, making these systems much easier to reason about.

To help JavaScript developers struggling with the complexity created by greater demands on their software, I've outlined the features in the JavaScript language that can help create more functional applications, as well as some third-party libraries that make functional programming easier.

 

Why functional programming?

Let's say you put bread in the toaster, set it to the lowest level and push the toast button. A few minutes later while you're busy making eggs, the toast pops up, and it's burnt. You don't understand why. But after looking carefully at the state of the toaster, you notice that it's set to the highest setting.

What happened? It turns out that when you went to the fridge to get some eggs, opening the door caused the toaster to change its level to the highest setting. The toaster's level was changed by a seemingly unrelated action, i.e. the opening of the refrigerator, that had the side-effect of mutating the toaster's darkness setting.

If people had to endure this type of world, they'd all go crazy. But this is the world that we software developers endure, day in and day out. We set variables that some other operation changes. We call functions that have undocumented or non-intuitive side-effects.

In functional programming, this craziness is forbidden. The hope is that by controlling side-effects, local effects stay local. And by making data immutable, we can trust that no other part of the code will pull the rug out from under us—even our own code.

If we can have these guarantees in the software world, much as we do in the real world, that it makes it far easier to reason about our code. Our expectations are limited and, therefore, bugs reduced.

Functional programming in JavaScript

While this sounds great for developers who program in functional languages, it doesn't leave much hope for JavaScript programmers. But all is not lost. JavaScript has many features of functional programming languages, and developers have built libraries to fill in the gaps where it's lacking. Here are the key areas where JavaScript, and several libraries, can help us.

Immutability: Can't touch this

Immutability is a guarantee that once we set a value, we can trust that it won't change, ever.

JavaScript supports a poor man's immutability with the const keyword:

const cantTouchThis = 4;
cantTouchThis = 3; // throws an exception

But const is limited when dealing with objects. That is the object variable is immutable, but the object's properties are not:

const canTouchSomeOfThis = {
    a: 1,
    b: 2
};

canTouchSomeOfThis = {}; // throws an exception
canTouchSomeOfThis.a = 3; // THIS IS ALLOWED!!!!

There's another solution, Object.freeze, but it too has its limits:

const cantTouchThisSortOf = {
    a: 1,
    b: {
        x: 10,
        y: 20
    }
};

Object.freeze(cantTouchThisSortOf);

cantTouchThisSortOf = {}; // throws an exception
cantTouchThisSortOf.a = 3; // throws an exception
cantTouchThisSortOf.b = {}; // throws an exception
cantTouchThisSortOf.b.x = 1; // THIS IS ALLOWED!!!!

We can get one level of immutability with const and one more with Object.freeze,but that's it for built-in solutions for JavaScript. We could write a recursive deepFreeze function to freeze the whole object at all levels, but that would be our responsibility. Here's an example from the Mozilla Developer Network:

function deepFreeze(obj) {

  // Retrieve the property names defined on obj
  var propNames = Object.getOwnPropertyNames(obj);

  // Freeze properties before freezing self
  propNames.forEach(function(name) {
    var prop = obj[name];

    // Freeze prop if it is an object
    if (typeof prop == 'object' && prop !== null)
      deepFreeze(prop);
  });

  // Freeze self (no-op if already frozen)
  return Object.freeze(obj);
}
This is far from ideal, but it is workable.

However, what happens when we want to modify something in our immutable structure? You might think this is a silly question but think about strings for a moment. Strings are immutable. Even though it seems as though we're mutating a string, we're actually getting another copy of the string with those mutations:

var str = "Cannot touch this";
var oldStr = str;
str = str.toUpperCase(); // this is where the copy happens
console.log(oldStr); // prints Cannot touch this
console.log(str); // prints CANNOT TOUCH THIS

If str was mutable, then oldStr would have also changed. But that's not the case. We simply got a copy of the string with each character uppercased. The mutation happened in toUpperCase, but only to the copy.

The same is true with complex data structures, e.g. a multi-leveled object:

const multiLeveled = {
    a: {
        r: 1,
        s: 2
    },
    b: {
        t: 3
        u: 4
    },
    c: {
        x: {
            v: 5,
            w: 6
        },
        y: {
            z: {
                a: 1,
                b: 2
            }
        }
    }
};

deepFreeze(multilevel);

To make changes to multiLevel, we must clone it first. Then modifications can be made to the clone. Finally, we freeze the clone. It's a lot of work, but it's still doable.

As our structures get larger and more complex, cloning becomes expensive. What we need are data structures that can be cloned and mutated efficiently, while not affecting the original.

Structural sharing: Share and share alike

Efficient data structures use a technique called structural sharing. When a data structure is mutated, a copy is made that shares as much as it can from the original data structure. The only data that's unique to the copy is the data that changed from the original.

Most functional languages have data structures that use structural sharing. Unfortunately, JavaScript does not. But there is a JavaScript library that does: Immutable.js.

Here's a simple example from its website:

var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50

What's not apparent in this example is that the keys a and c are shared between map1 and map2. Only key b is unique to each. This is a very efficient use of both time and space. It's efficient in time, since those keys do not need to be copied, and efficient in space, since most of the data is shared.

This is essentially what functional languages do under the hood. Now you can have the same in JavaScript, but at a cost. Immutable's API makes our code look more like Java than JavaScript.

It's not the end of the world, but it's not great either. If you want immutability, you have to live with this, because there is no native solution.

No loops for you

There are no loop constructs in functional languages. Instead, you do iteration using recursion. Typically, functional iteration is done via map, filter, or reduce/fold. These functions abstract the iteration process over arrays (or lists).

In JavaScript, these functions are built into Array. The problem is that they're not curried, which is unfortunate, because it greatly limits their usefulness.

Once again, there's a library, Ramda, that provides curried versions of these functions, and more.

Here's a simple example in Node.js (also supported in the browser):

const R = require('ramda');

const mult = R.curry((a, b) => a * b);
const multBy10 = mult(10);

const a = [1, 2, 3, 4, 5];
const aTimes10 = R.map(multBy10, a);
console.log(aTimes10); // prints [10, 20, 30, 40, 50]

R.curry takes a standard JavaScript function and makes it curried, i.e. you can provide as few or as many parameters at a time as you like. Once all parameters have been provided, then the function executes.

Here R.map provides the second and final parameter to multBy10.

Ramda never mutates any object: It will always return a mutated copy. Note that the data structures are not optimized for immutability, so performance in space and time are considerations. But in most cases, this shouldn't be an issue.

This is just the tip of the Ramda iceberg. Here are some other features the library has:

  1. Functional composition
  2. Functors
  3. Memoizing 
  4. Transducers
  5. Limited supports of the Fantasy Land Specification
  6. Lenses

Functional architecture: Bigger and better

So far, we've only considered functional language constructs and libraries for JavaScript. But what about functional paradigms at the architectural level?

Maybe you've heard of Redux. Billed as a predictable state container for JavaScript apps, it's used for the model portion of common application patterns, e.g. MVC, MVP, MVVM, etc. Two utilities that influenced Redux: Flux (by Facebook) and Elm (by Evan Czaplicki).

Redux follows three fundamental principles:

  1. Single source of truth – There is one state object that contains all of the application's state.
  2. State is read only – All state changes are centralized and transformed in a strict order.
  3. Changes made with pure functions – Pure reducers operate on previous state and produce the next state.

Here Redux is using immutability and state management—major features of the functional paradigm—to control complexity and simplify how developers think about their applications.

Work, work and more work

While all of these libraries and techniques make JavaScript more functional and reliable, it's a lot of work. The developer is responsible for making sure that certain principles are adhered to, and that certain libraries are used exclusively.

At this point, it should be clear that JavaScript isn't up to the job. Even with libraries, it falls short of the ideal. It's too bad that we have no choice as to which languages we can use on the front-end. Or do we?

A pile of transpilers

The fact that browsers only support JavaScript has limited front-end developers for decades. But today, there are many functional languages that compile, or more accurately, transpile to JavaScript:

  1. Fable and Websharper (F#)
  2. PureScript (Haskell)
  3. ClojureScript (Clojure)
  4. Reason (OCaml)
  5. Elm (ML-based)

Fable is a lean F#-to-JavaScript compiler. Unfortunately, it doesn't provide any specific tools for web development. Websharper is a more ambitious framework .for both client and server, that supports C# and F#. It's rooted heavily in the .NET world, but it is not a pure functional solution.

If you are a .NET programmer, these options will be the most familiar.

  • PureScript is a Haskell-to-JavaScript compiler, which means that it has the same steep learning curve as Haskell. Its library support is also quite extensive.
  • Haskell programmers usually prefer to use PureScript for the front end, especially if they are coding Haskell on the back end.
  • ClojureScript is a Clojure-to-JavaScript compiler. It's a LISP-like language, as is ClojureScript, and has extensive library support.
  • Reason is Facebook's OCaml-to-JavaScript compiler. OCaml is not strictly functional. So, like JavaScript, the onus is on the developer to adhere to functional practices.
  • Elm is an ML-based, pure functional language that compiles to JavaScript. Its functional reactive programming (FRP) architecture is what inspired, Redux, Purescript-elm, and Cycle.js. It's main focus is to make web development fast, easy, and reliable. Elm has eliminated runtime errors from front-end development. Its library support is growing fast, and so is its community.

Looking to the future of functional JavaScript

Since JavaScript is an evolving language, who knows what new functional capabilities will wind up in the next version (if you don't mind waiting).

There are many who can't wait, or don't want to take on the huge burden of enforcing functional techniques through discipline. They are breaking away from the old ball-and-chain and using JavaScript as a target language.

Instead of writing in it, they're compiling to it. Frankly speaking, this approach dates back to the CoffeeScript days. So JavaScript developers are familiar with this approach, i.e. coding in one language, and delivering JavaScript.

As WebAssembly becomes more mature and ubiquitous, you may just skip JavaScript altogether. This is the plan for Elm, my preferred pure functional language for web development.

No matter which path you choose, however, the new JavaScript world is becoming increasingly functional, and there are many paths to that future. Hopefully, yours is a good one.

 

Keep learning

Read more articles about: App Dev & TestingApp Dev