• No results found

Pure functions and the problem with side effects

This chapter covers

1.2 What is functional programming?

1.2.2 Pure functions and the problem with side effects

Functional programming is based on the premise that you build immutable pro-grams based on the building blocks of pure functions. A pure function has the fol-lowing qualities:

It depends only on the input provided and not on any hidden or external state that may change during its evaluation or between calls.

It doesn’t inflict changes beyond their scope, such as modifying a global object or a parameter passed by reference.

Intuitively, any function that doesn’t meet these requirements is “impure.” Program-ming with immutability can feel strange at first. After all, the whole point of impera-tive design, which is what we’re accustomed to, is to declare that variables are to mutate from one statement to the next (they’re “variable,” after all). This is a natural thing for us to do. Consider the following function:

var counter = 0;

function increment() { return ++counter;

}

This function is impure because it reads/modifies an external variable, counter, which isn’t local to the function’s scope. Generally, functions have side effects when reading from or writing to external resources, as shown in figure 1.1. Another exam-ple is the popular function Date.now(); its output certainly isn’t predicable and con-sistent, because it always depends on a constantly changing factor: time.

Translating lambda notation to regular function notation

Lambda expressions provide an enormous syntactical advantage over regular func-tion notafunc-tions because they reduce the structure of a funcfunc-tion call down to the most important pieces. This ES6 lambda expression

num => Math.pow(num, 2)

is equivalent to the following function:

function(num) {

return Math.pow(num, 2);

}

In this case, counter is accessed via an implicit global variable (in browser-based JavaScript, it’s the window object). Another common side effect occurs when accessing instance data via the this keyword. The behavior of this in JavaScript is unlike it is in any other programming language because it determines the runtime context of a function. This often leads to code that’s hard to reason about, which is why I avoid it when possible. I revisit this topic in the next chapter. Side effects can occur in many situations, including these:

Changing a variable, property, or data structure globally

Changing the original value of a function’s argument

Processing user input

Throwing an exception, unless it’s caught within the same function

Printing to the screen or logging

Querying the HTML documents, browser cookies, or databases

If you’re unable to create and modify objects or print to the console, what practical value would you get from a program like this? Indeed, pure functions can be hard to use in a world full of dynamic behavior and mutation. But practical functional pro-gramming doesn’t restrict all changes of state; it just provides a framework to help you manage and reduce them, while allowing you to separate the pure from the impure.

Impure code produces externally visible side effects like those listed earlier, and in this book I examine ways to deal with this.

To talk more concretely about these issues, suppose you’re a developer on a team implementing an application to manage a school’s student data. Listing 1.3 shows a small imperative program that finds a student record by Social Security number and renders it in the browser (again, the use of the browser is immaterial; you could just as easily write to the console, a database, or a file). I refer to and expand this program throughout the book as a typical, real-world scenario that involves side effects by inter-acting with an external local object store (like an array of objects) and doing some level of IO.

function increment () { return ++counter;

}



Side effect:

Global reference was changed var counter = 0;

Global variable

Function boundary Figure 1.1 Function increment()

causes side effects by reading/

modifying an external variable, counter. Its result is unpredictable because counter can change at any time between calls.

function showStudent(ssn) {

throw new Error('Student not found!');

} }

showStudent('444-44-4444');

Let’s analyze this code further. This function clearly exposes a few side effects that rip-ple beyond its scope:

It interacts with an external variable (db) for data access because the function signature doesn’t declare this parameter. At any point in time, this reference could become null or change from one call to the next, yielding completely different results and compromising the integrity of the program.

The global variable elementId can change at any time, outside your control.

HTML elements are directly modified. The HTML document (DOM) is itself a mutable, shared, global resource.

It can potentially throw an exception if the student isn’t found, which causes the entire program stack to unwind and end abruptly.

The function in listing 1.3 relies on external resources, which makes the code inflexi-ble, hard to work with, and difficult to test. Pure functions, on the other hand, have clear contracts as part of their signatures that describe clearly all of the function’s for-mal parameters (set of inputs), making them simpler to understand and use.

Let’s put our functional hat on and use what you learned from the simple print-Message program against this real-life scenario. As you become more comfortable with functional programming in this book, you’ll continue to improve this implementation with new techniques. At the moment, you can make two simple enhancements:

Separate this long function into shorter functions, each with a single purpose.

Reduce the number of side effects by explicitly defining all arguments needed for the functions to carry out their job.

Let’s begin by separating the activities of fetching the student record from displaying it on the screen. Granted, the side effects from interacting with an external storage

Listing 1.3 Imperative showStudent function with side effects

Accesses object storage to look up a student by SSN. Assume this is a synchronous operation for now; I deal with asynchronous code much later in the book.

Reaches outside Runs this program with

SSN 444-44-4444 and appends the student details to the page

system and the DOM are unavoidable, but at least you can make them more manage-able and single them out from the main logic. To do this, I’ll introduce a popular FP technique called currying. With currying, you can partially set some of the arguments of a function in order to reduce them down to one. As shown in the next listing, you can apply curry to reduce find and append to unary functions that can easily com-bine via run.

var find = curry(function (db, id) { var obj = db.get(id);

if(obj === null) {

throw new Error('Object not found!');

}

return obj;

});

var csv = (student) {

return `${student.ssn}, ${student.firstname}, ${student.lastname}`;

};

var append = curry(function (elementId, info) {

document.querySelector(elementId).innerHTML = info;

});

You don’t need to understand currying now, but it’s important to see that being able to reduce the length of these functions lets you write showStudent as the combination of these smaller parts:

var showStudent = run(

append('#student-info'), csv,

find(db));

showStudent('444-44-4444');

Although this program has been only marginally improved, it’s beginning to show many benefits:

It’s a lot more flexible, because it now has three reusable components.

This fine-grained function reuse is a strategy for increasing your productivity, because you can dramatically reduce the footprint of code that must be actively managed.

You enhance the code’s readability by following a declarative style that provides a clear view of the high-level steps carried out by this program.

More important, interaction with the HTML objects is moved into its own func-tion, isolating the pure from the non-pure (impure) behavior. I explain curry-ing and managcurry-ing pure and impure parts in depth in chapter 4.

Listing 1.4 Decomposing the showStudent program

The find function needs a reference to the object store and the ID of the student to look up.

Converts a student object into comma-separated values

To display a student’s details on the page, you need the element ID and the student data.

Partially sets the HTML element ID to use in the function

Partially sets a data access object to point to the students table

This program still has some loose ends that need to be tightened, but reducing side effects will make it less brittle to changing external conditions. If you look closer at the find function, you’ll notice it has a null-check branching statement that can produce an exception. For many reasons, which we’ll study later, it’s beneficial to guarantee a consistent return value from a function, making its result consistent and predicable. This is a quality of pure functions called referential transparency.