At about this time, there is another important question that you should be asking: Is JavaScript a functional language? Usually, when thinking about FP, the list of languages that are mentioned does not include JavaScript, but does include less common options, such as Clojure, Erlang, Haskell, and Scala; however, there is no precise definition for FP languages or a precise set of features that such languages should include. The main point is that you can consider a language to be functional if it supports the common programming style associated with FP. Let's start by learning about why we would want to use JavaScript at all and how the language has evolved to its current version, and then see some of the key features that we'll be using to work in a functional way.
Is JavaScript functional?
JavaScript as a tool
What is JavaScript? If you consider popularity indices, such as the ones at www.tiobe.com/tiobe-index/ or http://pypl.github.io/PYPL.html, you'll find that JavaScript is consistently in the top ten most popular languages. From a more academic point of view, the language is sort of a mixture, borrowing features from several different languages. Several libraries helped the growth of the language by providing features that weren't so easily available, such as classes and inheritance (today's version of the language does support classes, but that was not the case not too long ago), that otherwise had to be achieved by doing some prototype tricks.
JavaScript has grown to be incredibly powerful. But, as with all power tools, it gives you a way to not only produce great solutions, but also to do great harm. FP could be considered as a way to reduce or leave aside some of the worst parts of the language and focus on working in a safer, better way; however, due to the immense amount of existing JavaScript code, you cannot expect it to facilitate large reworkings of the language that would cause most sites to fail. You must learn to live with the good and the bad, and simply avoid the latter parts.
In addition, the language has a broad variety of available libraries that complete or extend the language in many ways. In this book, we'll be focusing on using JavaScript on its own, but we will make references to existing, available code.
If we ask whether JavaScript is actually functional, the answer will be, once again, sorta. It can be seen as functional because of several features, such as first-class functions, anonymous functions, recursion, and closures—we'll get back to this later. On the other hand, it also has plenty of non-FP aspects, such as side effects (impurity), mutable objects, and practical limits to recursion. So, when programming in a functional way, we'll be taking advantage of all the relevant, appropriate language features, and we'll try to minimize the problems caused by the more conventional parts of the language. In this sense, JavaScript will or won't be functional, depending on your programming style!
If you want to use FP, you should decide which language to use; however, opting for fully functional languages may not be so wise. Today, developing code isn't as simple as just using a language; you will surely require frameworks, libraries, and other sundry tools. If we can take advantage of all the provided tools but at the same time introduce FP ways of working in our code, we'll be getting the best of both worlds, never mind whether JavaScript is functional!
Going functional with JavaScript
JavaScript has evolved through the years, and the version we'll be using is (informally) called JS10, and (formally) ECMAScript 2019, usually shortened to ES2019 or ES10; this version was finalized in June 2019. The previous versions were as follows:
- ECMAScript 1, June 1997
- ECMAScript 2, June 1998, which was basically the same as the previous version
- ECMAScript 3, December 1999, with several new functionalities
- ECMAScript 5, December 2009 (and no, there never was an ECMAScript 4, because it was abandoned)
- ECMAScript 5.1, June 2011
- ECMAScript 6 (or ES6; later renamed ES2015), June 2015
- ECMAScript 7 (also ES7, or ES2016), June 2016
- ECMAScript 8 (ES8 or ES2017), June 2017
- ECMAScript 9 (ES9 or ES2018), June 2018
You can read the standard language specification at www.ecma-international.org/ecma-262/7.0/. Whenever we refer to JavaScript in the text without further specification, ES10 (ES2019) is what is being referred to; however, in terms of the language features that are used in the book, if you were just to use ES2015, then you'd mostly have no problems with this book.Â
No browsers fully implement ES10; most provide an older version, JavaScript 5 (from 2009), with an (always growing) smattering of features from ES6 up to ES10. This will prove to be a problem, but fortunately, a solvable one; we'll get to this shortly. We'll be using ES10 throughout the book.
As we are going to work with JavaScript, let's start by considering its most important features that pertain to our FP goals.
Key features of JavaScript
JavaScript isn't a purely functional language, but it has all the features that we need for it to work as if it were. The main features of the language that we will be using are as follows:Â
- Functions as first-class objects
- Recursion
- Arrow functions
- Closures
- Spread
Let's see some examples of each one and find out why they will be useful to us. Keep in mind, though, that there are more features of JavaScript that we will be using; the upcoming sections just highlight the most important features in terms of what we will be using for FP.
Functions as first-class objects
Saying that functions are first-class objects (also called first-class citizens) means that you can do everything with functions that you can do with other objects. For example, you can store a function in a variable, you can pass it to a function, you can print it out, and so on. This is really the key to doing FP; we will often be passing functions as parameters (to other functions) or returning a function as the result of a function call.Â
If you have been doing async Ajax calls, then you have already been using this feature: a callback is a function that will be called after the Ajax call finishes and is passed as a parameter. Using jQuery, you could write something like the following:
$.get("some/url", someData, function(result, status) {
// check status, and do something
// with the result
});
The $.get() function receives a callback function as a parameter and calls it after the result is obtained.Â
Since functions can be stored in variables, you could also write something like the following. Pay attention to how we use the doSomething variable in the $.get(...) call:
var doSomething = function(result, status) {
// check status, and do something
// with the result
};
$.get("some/url", someData, doSomething);
We'll be seeing more examples of this in Chapter 6, Producing Functions – Higher-Order Functions.
Recursion
Recursion is the most potent tool for developing algorithms and a great aid for solving large classes of problems. The idea is that a function can at a certain point call itself, and when that call is done, continue working with whatever result it has received. This is usually quite helpful for certain classes of problems or definitions. The most often quoted example is the factorial function (the factorial of n is written as n!) as defined for nonnegative integer values:
- If n is 0, then n!=1
- If n is greater than 0, then n! = n * (n-1)!
This can be immediately turned into code:
function fact(n) {
if (n === 0) {
return 1;
} else {
return n * fact(n - 1);
}
}
console.log(fact(5)); // 120
Recursion will be a great aid for the design of algorithms. By using recursion, you could do without any while or for loops—not that we want to do that, but it's interesting that we can! We'll be devoting the entirety of Chapter 9, Designing Functions – Recursion, to designing algorithms and writing functions recursively.
Closures
Closures are a way to implement data hiding (with private variables), which leads to modules and other nice features. The key concept of closures is that when you define a function, it can refer to not only its own local variables but also to everything outside of the context of the function. We can write a counting function that will keep its own count by means of a closure:
function newCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const nc = newCounter();
console.log(nc()); // 1
console.log(nc()); // 2
console.log(nc()); // 3
Even after newCounter()Â exits, the inner function still has access to count, but that variable is not accessible to any other parts of your code.
We'll find several uses for closures, such as memoization (see Chapter 4, Behaving Properly – Pure Functions, and Chapter 6, Producing Functions – Higher-Order Functions) and the module pattern (see Chapter 3, Starting out with Functions – A Core Concept, and Chapter 11, Implementing Design Patterns – The Functional Way), among others.
Arrow functions
Arrow functions are just a shorter, more succinct way of creating an (unnamed) function. Arrow functions can be used almost everywhere a classical function can be used, except that they cannot be used as constructors. The syntax is either (parameter, anotherparameter, ...etc) => { statements } or (parameter, anotherparameter, ...etc) => expression. The first allows you to write as much code as you want, and the second is short for { return expression }. We could rewrite our earlier Ajax example as follows:
$.get("some/url", data, (result, status) => {
// check status, and do something
// with the result
});
A new version of the factorial code could be like the following code:
const fact2 = n => {
if (n === 0) {
return 1;
} else {
return n * fact2(n - 1);
}
};
console.log(fact2(5)); // also 120
You would probably write the latter as a one-liner—can you see the equivalence to our earlier code? Using a ternary operator in lieu of an if is quite common:
const fact3 = n => (n === 0 ? 1 : n * fact3(n - 1));
console.log(fact3(5)); // again 120
With this shorter form, you don't have to write return—it's implied.
There's one other small thing to bear in mind: when the arrow function has a single parameter, you can omit the parentheses around it. I usually prefer leaving them, but I've applied a JS beautifier, Prettier, to the code, which removes them. It's really up to you whether to include them or not! (For more on this tool, check out https://github.com/prettier/prettier.) By the way, my options for formatting were --print-width 75 --tab-width 2 --no-bracket-spacing.
Spread
The spread operator (see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_operator) lets you expand an expression in places where you would otherwise require multiple arguments, elements, or variables. For example, you can replace arguments in a function call, as shown in the following code:
const x = [1, 2, 3];
function sum3(a, b, c) {
return a + b + c;
}
const y = sum3(...x); // equivalent to sum3(1,2,3)
console.log(y); // 6
You can also create or join arrays, as shown in the following code:
const f = [1, 2, 3];
const g = [4, ...f, 5]; // [4,1,2,3,5]
const h = [...f, ...g]; // [1,2,3,4,1,2,3,5]
It works with objects too:
const p = { some: 3, data: 5 };
const q = { more: 8, ...p }; // { more:8, some:3, data:5 }
You can also use it to work with functions that expect separate parameters instead of an array. Common examples of this would be Math.min() and Math.max():
const numbers = [2, 2, 9, 6, 0, 1, 2, 4, 5, 6];
const minA = Math.min(...numbers); // 0
const maxArray = arr => Math.max(...arr);
const maxA = maxArray(numbers); // 9
You can also write the following equality since the .apply() method requires an array of arguments, but .call() expects individual arguments:
someFn.apply(thisArg, someArray) === someFn.call(thisArg, ...someArray);
Using the spread operator helps write a shorter, more concise code, and we will be taking advantage of it. We have seen all of the most important JavaScript features that we will be using. Let's round off the chapter by looking at some tools that we'll be working with.