JavaScript's Odd Parts – Configurable Functions as a DSL

by
Tags: , ,
Category:

As some of you may be moving from Java to JavaScript, you’ll find there are a number of ways to program – using objects, functions, or a hybrid approach. A common style of API you’ll see in the wild lends itself to expressive object definitions, while at the same time being easy to use to express a domain-specific language syntax.

Configurable Functions

This blog post was inspired by the D3 charting API’s author, Mike Bostock, and his article, Toward Configurable Functions. When I first looked at the D3 API, I noticed every sample was created in a way that used purely function calls:

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

Even if you know nothing about the D3 Graphing Library, you should be able to infer that the developer selects the body tag, adds an svg element, and sets the width and height attributes, all by chaining four methods to the d3 ‘object’.
This is an interesting syntax, not the least because you can use it to express your domain in a relatively functional way, without a lot of visible object creation. For example, let’s build a domain model for an automobile, one that lets us set the color and speed, and that determines a driving state based on whether the speed is positive, negative, or zero.

var car = window.automobile()
                .speed(10)
                .color('red');

Kind of nifty, eh? D3 isn’t the only framework that works this way – consider jQuery. It uses a functional calling style as well – for example: $('a').attr('text-color', 'red');. So how do we implement this kind of API for our own objects?

It’s functions, almost all the way down…

We begin by building a function which we call to create our object. We also place variables within this function that will serve as our state. They are automatically hidden by default due to the fact we’re not exposing them on anything. Observe:

function automobile() {
     // hidden state
     var color = 'blue', speed = 0, state ='parked';
}

Now, you may be wondering why we’re defining these methods on a function if the scope of the function (the variable context) dies when we leave the function. If you’re wondering this, you may not know about the JavaScript closure property, which states that (roughly):
Any variable defined in the context of a function creating another object may be captured by the returned object.
Wait, what? This means that if I build and then return an object from the automobile() function, it can reference and use the color, speed and state variables within it, and treat them like private state.

Building the implementation of our automobile

Armed with this information, our goal now is to return something that uses those state variables and represents an automobile.
An object-oriented developer might directly jump to something like this:

function Automobile(color, speed) {
     this.color = color || 'blue';
     this.speed = speed || 0;
     return {
           getColor: function () { return this/or; },
           setColor: function(col) { color = col; },
           getSpeed: ...
     };
}
var myAuto = new Automobile('blue', 100);
myAuto.setColor('black');

And that’d be OK, except it’s not very expressive. First, the constructor function. Yes, it feels like a Java programmer writing JavaScript code in the Java style (that was the original idea behind the constructor function and the new keyword). And of course, there is no direct protection of variables in Javascript if they’re owned by an object, unless you go down the route of defining ES5 setters and/or getters which is more verbose.

Leave your objects behind

Let’s find a more idiomatic way to do this in JavaScript. We can provide a function that creates and returns another function. This returned function would create the automobile using closures and state, maintained on the instance of the function. These closure functions provide operators and information on the state of the thing being built. Let’s look at a small subset of the implementation, one that manipulates the color:

function automobile(_color, _speed, _state) {
    var color = 'blue', speed = 0, state = 'parked';
    function implementation() {
        // could customize initialization here
    }
    implementation.color = function(value) {
       if (value) {
           color = value;
           return implementation;
       } else {
           return color;
       }
     };
     return implementation;
}

We get a few benefits from this approach. First, we get a default color, blue, unless we call auto().color('red'). If we do make that call, we get a new auto, it sets the color to red, and then returns the auto. This lets us chain multiple operations to each other in a DSL style, while still returning the function with the properties at the end. Maybe we don’t even need an object, and perhaps the last thing we do is just call some function that does some DOM manipulation or configures a JS event. The options are pretty wide open.
Pretty nifty, eh?
There is a bit of a downside to this approach. For starters, using typeof we see that the type returned is function, not object. That is a bit strange, except when you remember that functions can contain properties. Also, I’m not sure it is really a replacement for pure “value objects” as we know them, unless perhaps you chain a valueOf() or build() method to the end, which would return a pure object with those properties.
With that said, here is the full implementation:

(function() {
    'use strict';
    function automobile() {
        // hidden state
        var color = 'blue', speed = 0, state ='parked';
        // this is the function that we'll return, with other properties
        // that are ALSO functions.
        function implementation() {
        }
        implementation.speed = function() {
            if (arguments[0]) {
                speed = arguments[0];
                if (speed > 0) {
                    state = 'forward';
                } else if (speed === 0) {
                    state = 'parked';
                } else if (speed < 0) {
                    state = 'reverse';
                }
                return implementation;
            } else {
                return speed;
            }
        };
        // only set via affects from other functions
        implementation.state = function() {
            return state;
        };
        // change or view color
        implementation.color = function(val) {
            if (val) {
                color = val;
                return implementation;
            } else {
                return color;
            }
        };
        return implementation;
    };
    // make available on window
    window.automobile = automobile;
}());

Here are some uses of the API:

var car = automobile();
var car = automobile().color('red');
var car = automobile().speed(10);
var car = automobile().speed(10).color('red');
// perhaps this is better - provide a method to finalize it
var car = automobile().speed(10).color('red').build();