About this time, another important question that you should be asking: Is JS a functional language? Usually, when thinking about FP, the mentioned languages do not include JS, but do listless common options, such as Clojure, Erlang, Haskell, or 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.
Is JavaScript functional?
JavaScript as a tool
What is JS? 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 JS consistently is in the top ten of popularity. From a more academic point of view, the language is sort of a mixture, with features from several different languages. Several libraries helped the growth of the language, by providing features that weren't so easily available, as classes and inheritance (today's version of JS does support classes, but that was not the case not too long ago) that otherwise had to be simulated by doing some prototype tricks.
JS has grown to be incredibly powerful. But, as with all power tools, it gives you a way to produce great solutions, and also to do great harm. FP could be considered to be 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 JS code, you cannot expect large reworkings of the language that would cause most sites to fail. You must learn to live on with the good and the bad, and simply avoid the latter parts.
In addition, JS 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 JS on its own, but we will make references to existing, available code.
If we ask if JS is actually functional, the answer will be, once again, sorta. JS can be considered to be 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, JS 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 JS language features, and we'll try to minimize the problems caused by the more conventional parts of the language. In this sense, JS will or won't be functional, depending on your programming style!
If you want to use FP, you should decide upon 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 — and never mind if JS is or isn't functional!
Going functional with JavaScript
JS has evolved through the years, and the version we'll be using is (informally) called JS8, and (formally) ECMAScript 2017, usually shortened to ES2017 or ES8; this version was finalized in June 2017. The previous versions were:
- ECMAScript 1, June 1997
- ECMAScript 2, June 1998, basically the same as the previous version
- ECMAScript 3, December 1999, with several new functionalities
- ECMAScript 5 appeared only in December 2009 (and no, there never was an ECMAScript 4, because it was abandoned)
- ECMAScript 5.1 was out in June 2011
- ECMAScript 6 (or ES6; later renamed ES2015) in June 2015
- ECMAScript 7 (also ES7, or ES2016) was finalized in June 2016
- ECMAScript 8 (ES8 or ES2017) was finalized in June 2017
You can read the standard language specification at www.ecma-international.org/ecma-262/7.0/. Whenever we refer to JS in the text without further specification, ES8 (ES2017) is meant. However, in terms of the language features that are used in the book, if your were just to use ES2015, you'd have no problems with this book.
No browsers fully implement ES8; most provide an older version, JavaScript 5 (from 2009), with a (always growing) smattering of ES6, ES7, and ES8 features. This will prove to be a problem, but fortunately, a solvable one; we'll get to this shortly, and we'll be using ES8 throughout the book.
Key features of JavaScript
JS isn't a functional language, but it has all the features we need to work as if it were. The main features of the language that we will be using are:
- Functions as first–class objects
- Recursion
- Arrow functions
- Closures
- Spread
Let's see some examples of each one, to explain why they will be useful to us.
Functions as First Class Objects
Saying that functions are first class objects (also: 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, 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:
$.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:
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, when we consider Higher–Order Functions.
Recursion
This is a 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 n!) as defined for non-negative 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 JS 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 complete 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 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:
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: among others, 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).
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 one allows you to write as much code as you want; the second is short for { return expression }. We could rewrite our earlier Ajax example as:
$.get("some/url", data, (result, status) => {
// check status, and do something
// with the result
});
A new version of the factorial code could be:
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?
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. A short comment: 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, and it 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 4 --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:
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:
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 equation. 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 shorter, more concise code, and we will be taking advantage of it.