Functional Programming Part 2: Higher-Order Functions & Closures

Lecture 7 min.



Please read carefully and without rushing. Before moving on, make sure you've understood everything. Each following part of the chapter builds on the previous one. If you rush, you'll almost certainly miss some nuances that may matter later on.

Refactoring

Functional Programming Part 2: Higher-Order Functions & Closures

Let's recall what refactoring is. Here's an example of JavaScript code:

function validateSsn(ssn) {
    if (/^\d{3}-\d{2}-\d{4}$/.exec(ssn))
        console.log('Valid SSN');
    else
        console.log('Invalid SSN');
}function validatePhone(phone) {
    if (/^\(\d{3}\)\d{3}-\d{4}$/.exec(phone))
        console.log('Valid Phone Number');
    else
        console.log('Invalid Phone Number');
}

We've all written code like this, and only later started to notice that these two functions are almost identical, differing only in a few spots (highlighted in bold).

Instead of copying validateSsn and then editing it to get validatePhone, it's better to create a single function and parameterize the data.

In this example we'll parameterize the input value, the regular expression, and the message (or at least the last part of it).

The code after refactoring:

function validateValue(value, regex, type) {
    if (regex.exec(value))
        console.log('Valid ' + type);
    else
        console.log('Invalid ' + type);
}

The ssn and phone parameters from the old example are now represented as value.

The regular expressions /^\d{3}-\d{2}-\d{4}$/ and /^\(\d{3}\)\d{3}-\d{4}$/ - as regex.

And finally, the last parts of the messages 'SSN' and 'Phone Number' - as type.

It's always better to have one function instead of two. Or worse yet, three, four, or ten functions. This makes your code clean and easy to maintain.

For example: if a bug shows up, you need to fix it in exactly one place, rather than searching through the entire codebase for every place this function COULD have been pasted and reworked.

But what happens when we run into a situation like this:

function validateAddress(address) {
    if (parseAddress(address))
        console.log('Valid Address');
    else
        console.log('Invalid Address');
}function validateName(name) {
    if (parseFullName(name))
        console.log('Valid Name');
    else
        console.log('Invalid Name');
}

Here parseAddress and parseFullName are functions that take a string and return true if it parses.

How do we refactor this case?

Well, we can use value for address and name, and type for 'Address' and 'Name', just like before, but instead of a regular expression we have a function here.

The only way out is to pass the function as a parameter…

Higher-Order Functions

Functional Programming Part 2: Higher-Order Functions & Closures

Many languages don't support passing functions as parameters. Some do, but that doesn't make things any simpler.

In functional programming, a function is a first-class citizen of the language. In other words, a function is just another value.

As long as functions are simply values, we can pass them as parameters.

Although JavaScript isn't a pure functional language, you can perform some functional operations with it. Here are the last two functions, turned into a single one by passing a parser function as a parameter called parseFunc.

function validateValueWithFunc(value, parseFunc, type) {
    if (parseFunc(value))
        console.log('Valid ' + type);
    else
        console.log('Invalid ' + type);
}

Our new function is called a higher-order function.

Higher-order functions either take functions as parameters, or return them, or both at once.

Now we can call our higher-order function for the four previous functions (this works in JavaScript because Regex.exec returns a truthy value when a match is found):

validateValueWithFunc('123-45-6789', /^\d{3}-\d{2}-\d{4}$/.exec, 'SSN');
validateValueWithFunc('(123)456-7890', /^\(\d{3}\)\d{3}-\d{4}$/.exec, 'Phone');
validateValueWithFunc('123 Main St.', parseAddress, 'Address');
validateValueWithFunc('Joe Mama', parseName, 'Name');

This is far better than having four nearly identical functions.

But notice the regular expressions. They're a bit unwieldy. Let's clean up our code by reorganizing it like this:

var parseSsn = /^\d{3}-\d{2}-\d{4}$/.exec;
var parsePhone = /^\(\d{3}\)\d{3}-\d{4}$/.exec;validateValueWithFunc('123-45-6789', parseSsn, 'SSN');
validateValueWithFunc('(123)456-7890', parsePhone, 'Phone');
validateValueWithFunc('123 Main St.', parseAddress, 'Address');
validateValueWithFunc('Joe Mama', parseName, 'Name');

That's better. Now, when we want to parse a phone number, we don't need to copy and paste the regular expression.

But imagine we have far more regular expressions to parse, not just parseSsn and parsePhone. Every time we create a regex-based parser, we have to remember to add .exec at the end. And believe me, it's easy to forget.

We can protect ourselves from this by creating a higher-order function that returns the exec method.

function makeRegexParser(regex) {
    return regex.exec;
}var parseSsn = makeRegexParser(/^\d{3}-\d{2}-\d{4}$/);
var parsePhone = makeRegexParser(/^\(\d{3}\)\d{3}-\d{4}$/);validateValueWithFunc('123-45-6789', parseSsn, 'SSN');
validateValueWithFunc('(123)456-7890', parsePhone, 'Phone');
validateValueWithFunc('123 Main St.', parseAddress, 'Address');
validateValueWithFunc('Joe Mama', parseName, 'Name');

In this example, makeRegexParser takes a regular expression and returns the exec method, which in turn takes a string. validateValueWithFunc will pass the string, value, to the parser function, that is, to exec.

parseSsn and parsePhone work just as effectively as before, and just like the exec method of regular expressions.

Honestly, this is a minor improvement, but it's shown here as an example of a higher-order function returning a function (translator's note: methods are functions too).

Even so, you can imagine the benefit of such changes if makeRegexParser were far more complex.

Here's another example of a higher-order function returning a function:

function makeAdder(constantValue) {
    return function adder(value) {
        return constantValue + value;
    };
}

Here we have makeAdder, which takes constantValue and returns adder - a function that will add the constant value to whatever variable is passed to it.

Here's how it can be used:

var add10 = makeAdder(10);
console.log(add10(20)); // prints 30
console.log(add10(30)); // prints 40
console.log(add10(40)); // prints 50

We create a function, add10, by passing the constant 10 to the makeAdder function, which returns a function that will in turn add 10.

Notice that the adder function has access to constantValue even after makeAdder has returned its value. This is because constantValue was already within its scope at the moment adder was created.

This behavior is quite significant, since without it, functions that return functions wouldn't be nearly as useful. So it's important to understand how they work and what this behavior is called.

It's called a closure.

Closures in Functional Programming

Functional Programming Part 2: Higher-Order Functions & Closures

Here's a purpose-built example of functions using a closure:

function grandParent(g1, g2) {
    var g3 = 3;
    return function parent(p1, p2) {
        var p3 = 33;
        return function child(c1, c2) {
            var c3 = 333;
            return g1 + g2 + g3 + p1 + p2 + p3 + c1 + c2 + c3;
        };
    };
}

In this example, child has access to its own local variables, to parent's variables, and to grandParent's variables.

parent has access to its own variables and to grandParent's variables.

grandParent only has access to its own variables.

(See the pyramid above for clarity.)

Here's an example of using all this:

var parentFunc = grandParent(1, 2); // returns parent()
var childFunc = parentFunc(11, 22); // returns child()
console.log(childFunc(111, 222)); // prints 738
// 1 + 2 + 3 + 11 + 22 + 33 + 111 + 222 + 333 == 738

Here, parentFunc holds parent's scope, because grandParent returns parent.

Likewise, childFunc holds child's scope, because parentFunc, which is essentially parent, returns child.

When a function is created, all the variables in its scope at the moment of creation remain available to it for its lifetime. A function exists as long as there's a reference to it. For example, child's scope exists as long as childFunc keeps referencing it.

A closure is a function's scope that persists thanks to a reference to that function.

Note that closures in JavaScript are a bit of a mixed blessing, due to the mutability of variables, that is, because they can change their value from the moment they're defined until the function being called returns.

Fortunately, variables in functional programming languages are immutable, which eliminates this common source of bugs and uncertainty.

Comments

To leave a comment

If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Lectures and tutorial on "Functional programming"

Terms: Functional programming