Function = Var + Return: A Functional Style of JavaScript Programming

Functional Programming with JavaScript isn't as hard as you think

Functional programming, for most working programmers, is exotic. I’ve been experimenting with it as a way to get cleaner code and to expand my mind a bit. Here I describe a minimal approach to JavaScript which makes it intensely functional.

Functional programming avoids side effects (also known as state or mutable data) by using functions that operate on other functions. It emphasizes expressions over commands, and computation over assignment. There are lots of functional languages, including Lisp (the original) and XSLT (the one I happen to know best).

JavaScript is a flexible language with first-class functions, so it supports a functional programming style. Usually people do this with a library such as Underscore.js, which provides array operations like “map” and “reduce” and function operations like “bind” and “compose”. This is very cool. For example, when you use “map”, you work at a higher level of abstraction. You don’t have to worry about a temporary index variable for iterating over the array, so the code becomes shorter and clearer.

However, this approach is not as functional as it could be. If you’re freely assigning values to variables, then you’re still relying on changing state. If your code is primarily a sequence of statements, it’s imperative or procedural, and you’re missing the peculiar beauty of functional code.

I especially like two things about eliminating mutable state. First, it keeps me focused on results. Data that I don’t actually need just drops out naturally. Second, it avoids bugs from surprising state changes. Often in debugging, the question is “How the hell was X set to 3?” If there are a lot of ways to set X (and especially if X is global), that might be a hard question. If there’s only one way, you know where to look.

Obviously you will need to change the state of something somewhere in your program. Most likely your code is modifying the DOM, or at least logging to the console, so that’s a side effect right there. But if your code is nontrivial, it will have large chunks that just manipulate data. Those chunks are candidates for a functional approach.

It turns out you can avoid side effects in JavaScript by focusing on expressions and eliminating statements, even such basic statements as “if” and “for”. In fact, you can write each function as a single “var” followed by a single “return”. Here’s how.

Start with a typical function:

function digitSumReport(x) {
    if (typeof x !== "number") {
	return "non-number, sum undefined";
    } else if (Math.floor(x) < x) {
	return "non-integer, sum undefined";
    } else if (x < 0) {
        return "negative number, sum undefined";
    }

    var s = String(x);
    var sum = 0;
    for (var i = 0; i < s.length; i++) {
        sum += parseInt(s.charAt(i));
    }

    return "sum " + sum;
};

After you read Douglas Crockford’s JavaScript: The Good Parts, it looks like this needs some cleanup. Move the var statements to the top to make their scope clear. Also, try to wrap separate calculations in separate functions.

var digitSumReport1 = function(x) {
    var typeError = function(x) {
        if (typeof x !== "number") {
	    return "non-number";
        } else if (Math.floor(x) < x) {
            return "non-integer";
        } else if (x < 0) {
            return "negative number";
        }
        return null;
    };
    var sum = function(x) {
        var s = String(x);
        var i, sum = 0;
        for (i = 0; i < s.length; i++) {
            sum += parseInt(s.charAt(i));
        }
        return sum;
    };

    var error = typeError(x);
    if (error !== null) {
        return error + ", sum undefined";
    }
    return "sum " + sum(x);
};

Interesting: this is starting to look like a bunch of expressions, including function literals. Let’s push it farther. Replace the if statements with ? : expressions. That works even if the if statement has many cases (it’s like the Lisp cond).

var digitSumReport2 = function(x) {
    var
    typeError = (typeof x !== "number" ? "non-number" :
        (Math.floor(x) < x) ? "non-integer" :
        (x < 0) ? "negative number" :
        null),
    sum = function(x) {
        var
        s = String(x),
        sum = 0,
        i = 0;
        for (; i < s.length; i++) {
            sum += parseInt(s.charAt(i));
        }
        return sum;
    };
    return typeError !== null ? typeError + ", sum undefined" :
        "sum " + sum(x);
};

That for loop is still a statement. Can we make it an expression? Normally a loop has a state that it changes each time around. But instead of changing that state, it could return a new state each time.

We’ll define a general library function for loops. The loop state is an array with two elements: an index, and some data. Each time around, the loop calculates a new index and new data. When the index runs out (when it’s null, let’s say), it returns the final data.

This way, the library hides the for statement, and the code focuses on the calculations.

var Library = {
    repeated: function(initialState, nextStateFunction) {
        for (var state = initialState; state[0] !== null;
         state = nextStateFunction(state)) {
        }
        return state[1];
    }
};

var digitSumReport3 = function(x) {
    var
    typeError = (typeof x !== "number" ? "non-number" :
        (Math.floor(x) < x) ? "non-integer" :
        (x < 0) ? "negative number" :
        null),
    sum = function(x) {
        var s = String(x);
        return Library.repeated([0, 0], function(state) {
            var
            i = state[0],
            sum = state[1];
            return i < s.length ? [i + 1, sum + parseInt(s.charAt(i))] :
             [null, sum];
        });
    };
    return typeError !== null ? typeError + ", sum undefined" :
        "sum " + sum(x);
};

That looks rather nice! Each function is a single var followed by a single return.

Here’s a more complicated example: the unit tests that I used to maintain the code examples above. This function makes a list of test cases, and checks each version of the digitSumReport function against each test case. Then it logs a report listing all failures. You can run it in a browser.

var logTest = function() {
    var
    log = function(line) {
        return console.log(line);
    },
    testCases = [
        [0, "sum 0"],
        [10, "sum 1"],
        [32, "sum 5"],
        ["32", "non-number, sum undefined"],
        [null, "non-number, sum undefined"],
        [10.7, "non-integer, sum undefined"],
        [-3, "negative number, sum undefined"]
    ],
    functionNames = [
        "digitSumReport",
        "digitSumReport1",
        "digitSumReport2",
        "digitSumReport3"
    ],
    assertEqualError = function(value, expected, message) {
        return value === expected ? null :
         "ASSERTION FAILED: " + message +
         ": expected " + expected + ", got " + value;
    },
    functionErrors = function(functionName) {
        return Library.repeated([0, []], function(state) {
            var
            i = state[0],
            testCase = testCases[i],
            errors = state[1];
            return testCase ? (function() {
                var
                input = testCase[0],
                output = testCase[1],
                description = functionName + "(" + input + ")",
                f = window[functionName],
                error = assertEqualError(f(input), output, description);
                return [i + 1, error ? errors.concat(error) : errors];
            })() : [null, errors];
        });
    },
    allFunctionErrors = function() {
        return Library.repeated([0, []], function(state) {
            var
            i = state[0],
            errors = state[1],
            name = functionNames[i];
            return name ? [i + 1, errors.concat(functionErrors(name))] :
             [null, errors];
        });
    };
    return (function() {
        var
        entries = [].
         concat("Testing...").
         concat(allFunctionErrors()).
         concat("...done.");
        return Library.repeated([0, null], function(state) {
            var
            i = state[0],
            entry = entries[i];
            return entry ? [i + 1, log(entry)] : [null, null];
        });
    })();
};

logTest();

This style makes an interesting shift in how the code feels. It clarifies the scope of each variable. If you want to know where a value comes from, you just look up to the variable’s initialization, either as a var or as a function parameter. This makes it easier to modify the code confidently. Here var is like the Lisp let, which sets up local values.

Here are some finicky details.

We’ve gotten rid of if and for, and most other statements are easy to deal with. switch is like if, and do and while are like for. However, if you need exception statements like try and throw, they don’t seem to fit well in this style.

For a simple function, you can omit the var. For a function that doesn’t return anything useful, you could omit the return, or explicitly return null or undefined.

The repeated function is a little awkward, and there are other approaches. For basic array iteration, you could use the each provided by Underscore.js or jQuery, or the forEach provided natively in recent versions of JavaScript. But eventually you will want a general-purpose loop for something more complicated, such as a merge sort. More traditionally, you could also do it with recursion, if you think the stack can stand it.

Like most interesting uses of JavaScript, this style will spend extra time on object creation and function calls, so you will have to watch the performance. For more hard-core experiments, see the Functional library, and Crockford’s The Little Javascripter.

Since an assignment is an expression, the expression that initializes a variable could modify another variable. Obviously that’s not in the spirit of this style.

But finicky details aside, this is a simple functional style which you can apply independently of functional libraries. Function = Var + Return. Try it!

tags: , ,