Basic Functional Programming Techniques

Functional programming can be hard to wrap your head around because it requires you to approach problems much differently than you would in imperative programming. It’s not inherently more complex, but when you’re used to thinking a certain way it’s difficult to break those habits and look at things from a different perspective. Both paradigms have different strengths and knowing both will make you a better programmer.

Many resources for learning functional programming start with principles like:

  • Referential transparency
  • Immutability
  • Lazy evaluation
  • Recursion
  • Type systems

These are great things to learn but they can be overwhelming at first and can give the impression that you can only use functional programming in a purely functional language.

Here we’ll describe some basic practical techniques to illustrate the way functional programming solves problems. We’ll be using JavaScript for the code samples, but these techniques can be used in almost any language.

Higher-Order Functions

A higher-order function is a function that takes another function as one of its arguments. This is useful because it can make your functions much more general.

For example, consider the following pattern where you want to process each element of a list and create a new list with the results.

var xs = [1, 2, 3];
var ys = [];

for (var i = 0; i < xs.length; ++i) {
    ys.push(xs[i] + 1);
}

Using a higher-order function, we can generalize this pattern whose canonical name is map.

function map(f, xs) {
    var ys = [];
    for (var i = 0; i < xs.length; ++i) {
        ys.push(f(xs[i]));
    }
    return ys;
}

Now we can accomplish the same thing by:

var xs = [1, 2, 3];
var ys = map(function(x) {return x + 1}, xs);

The best part is that we don’t have to write the map function ourselves because it already exists. So not only do higher-order functions let us generalize things we need for specific applications, they also make the standard library of a language more useful because it can be more general.

Another common example of a higher-order function is filter(p, xs) where p is a predicate function (returns a boolean) and xs is a list. filter only keeps the elements of xs for which p returns true. For example:

function even(x) {
    return x % 2 === 0;
}

filter(even, [1, 2, 3, 4]); // [2, 4]

Immutable Variables

Mutating variables usually makes your code harder to reason about which results in bugs. This can be a counter intuitive concept if you’re used to imperative programming in which mutating variables is necessary for almost everything, but here’s an example:

var xs = [1, 2, 3];
var a = foo(xs);
var b = bar(xs);

It’s very useful to know that xs is the same when you pass it to foo as when you pass it to bar. Programs are much harder to debug when any function you pass a variable to might change it.

In pure functional languages it’s actually impossible to mutate a variable. In this case all your variables essentially become constants so instead of setting the value of a variable, you just make a new variable. But the most important application of this principle is at the function boundary. Mutating local variables is usually fine, but as a general rule a function should never mutate one of its arguments.

Ideally a function wouldn’t mutate anything outside of its own scope, which leads us into the next section.

No Global State

It’s common knowledge that you should generally avoid global variables, but since variables are immutable in pure functional programming global state becomes impossible. So that means whatever your function needs to produce its result must be one of its parameters.

An example of using global state would be:

var users = [];

function addUser(user) {
    users.push(user);
}

function main() {
    var user = new User();
    addUser(user);
}

If we convert this to a more functional style we can’t have functions modifying a global users list. So they would have to take that list as a parameter:

function addUser(user, users) {
    return clone(users).push(user);
}

function main() {
    var user = new User();
    var users = addUser(user, []);
}

Note: the clone function is pseudocode. It doesn’t exist in JavaScript but is roughly equivalent to Array.prototype.slice.

This is better for many reasons. First, addUser is now more general and can be used with any user list instead of just the global one. As a bonus, this makes unit testing much easier. Second, all the data addUser depends on is now easily seen in its signature. Before, its dependency on the users list was hidden from the caller, but now it’s explicit so the caller knows that changing the users list will change the result of addUser.

Modularity

In functional programming it’s preferable to write many small functions instead of a large monolithic function. This promotes reuse and prevents code duplication. For a silly example, if you have:

function processXsYs(xs, ys) {
    for (var i = 0; i < xs.length; ++i) {
        xs[i] += 1;
    }
    for (var j = 0; j < ys.length; ++j) {
        ys[j] += 1;
    }
}

You can convert that to:

function processX(x) {
    return x + 1;
}

function processY(y) {
    return y + 1;
}

function processXsYs(xs, ys) {
    var resultXs = map(processX, xs);
    var resultYs = map(processY, ys);
}

This is useful because first, the processX function is more general than a processXsYs function. Now we can reuse processX anywhere we need to process xs. But second, even if we don’t end up reusing that function it’s still better for readability. The processX function is easier to read than when that logic is embedded in a for loop, and the processXsYs function is also much more readable because it’s clear what each step is doing. And third, this makes for better unit testing because we can test each function individually whereas before we only would have been able to test the monolithic processXsYs.

So we’ve essentially gone from one confusing function that was doing three things to three very simple functions that each do one thing. Obviously this sort of refactoring is very dependent on specific circumstances, but this is the direction we want to head in.

Conclusion

The techniques shown above are the practical results of the functional paradigm. The beauty of these practical techniques is that you can start using them right away in whatever language you’re using. You don’t have to re-write your entire code base; you can start slow and just use them when you see an appropriate place.

There is a whole theoretical side of functional programming that we haven’t discussed because it takes more effort to internalize and it can be abstract and daunting. If you have the inclination, learning more about the theoretical aspects will certainly help you use these techniques more effectively and give you an understanding of where they come from.

The things we’ve discussed here (especially modular functions) aren’t exclusive to the functional paradigm, but they tend to be neglected in imperative programming whereas functional programming places more importance on them.

For further study of functional programming we highly recommend learning a purely functional language like Haskell. A great resource for learning Haskell is the free e-book “Learn You a Haskell for Great Good”.