Book Image

Mastering JavaScript Functional Programming - Second Edition

By : Federico Kereki
Book Image

Mastering JavaScript Functional Programming - Second Edition

By: Federico Kereki

Overview of this book

Functional programming is a paradigm for developing software with better performance. It helps you write concise and testable code. To help you take your programming skills to the next level, this comprehensive book will assist you in harnessing the capabilities of functional programming with JavaScript and writing highly maintainable and testable web and server apps using functional JavaScript. This second edition is updated and improved to cover features such as transducers, lenses, prisms and various other concepts to help you write efficient programs. By focusing on functional programming, you’ll not only start to write but also to test pure functions, and reduce side effects. The book also specifically allows you to discover techniques for simplifying code and applying recursion for loopless coding. Gradually, you’ll understand how to achieve immutability, implement design patterns, and work with data types for your application, before going on to learn functional reactive programming to handle complex events in your app. Finally, the book will take you through the design patterns that are relevant to functional programming. By the end of this book, you’ll have developed your JavaScript skills and have gained knowledge of the essential functional programming techniques to program effectively.
Table of Contents (17 chapters)
1
Technical Requirements
14
Bibliography

Is JavaScript functional?

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.

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.

The name JavaScript was chosen to take advantage of the popularity of Java—just as a marketing ploy! Its first name was Mocha, then, LiveScript, and only then, JavaScript.

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
ECMA originally stood for European Computer Manufacturers Association, but nowadays the name isn't considered an acronym anymore. The organization is responsible for more standards other than JavaScript, including JSON, C#, Dart, and others. For more details, go to its site at www.ecma-international.org/.

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.

In fact, there are only a few differences between ES2016 and ES2015, such as the Array.prototype.includes method and the exponentiation operator, **. There are more differences between ES2017 and ES2016—such as async and await, some string padding functions, and more—but they won't impact our code. We will also be looking at alternatives for even more modern additions, such as flatMap(), in later chapters.

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. 

This is better solved, in a more modern way, by using promises or async/await, but for the sake of our example, the older way is enough. We'll be getting back to promises, though, in the section called Building better containers in Chapter 12Building Better Containers – Functional Data Types, when we discuss monads; in particular, see the section called Unexpected Monads - Promises.

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)!
The value of n! is the number of ways that you can order n different elements in a row. For example, if you want to place five books in line, you can pick any of the five for the first place, and then order the other four in every possible way, so 5! = 5*4!. If you continue to work this example, you'll get 5! = 5*4*3*2*1=120, so n! is the product of all numbers up to n.

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 9Designing 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.

This isn't a very good example of FP— a function (nc(), in this case) isn't expected to return different results when called with the same parameters!

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
Arrow functions are usually called anonymous functions because of their lack of a name. If you need to refer to an arrow function, you'll have to assign it to a variable or object attribute, as we did here; otherwise, you won't be able to use it. We'll learn more about this in the section called Arrow functions in Chapter 3, Starting out with Functions - A Core Concept.

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.

In lambda calculus, a function such as x => 2*x would be represented as λx.2*x. Although there are syntactical differences, the definitions are analogous. Functions with more parameters are a bit more complicated; (x,y)=>x+y would be expressed as λx.λy.x+y. We'll learn more about this in the section called Lambdas and functions, in Chapter 3Starting out with Functions - A Core Concept, and in the section called Currying, in Chapter 7Transforming Functions - Currying and Partial Application.

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);
If you have problems remembering what arguments are required by .apply() and .call(), this mnemonic may help: A is for an array, and C is for a comma. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call for more information.

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.