Functional decorators without coupling

Extend simple feature with little functional decorators.

Progressive enhancement starts with a little nugget of functionality and then enhances it in iterations. For example, lets create a function to add two numbers:

1
function add(a, b) { return a + b }

This one-liner is easy to write, code review and plug into production. It provides the little nugget of functionality, but soon people start demanding its enhancement with more features: logging, argument validation, etc. The typical enhancement proceeds by cramming all features into the function itself, tightly coupling everything together. Our beautiful addition becomes this convoluted code:

1
2
3
4
5
6
function add(a, b) {
console.log('adding', a, 'to', b);
console.assert(typeof a === 'number', 'first argument ' + a + ' is not a number');
console.assert(typeof b === 'number', 'second argument ' + b + ' is not a number');
return a + b;
}

Because of the tight coupling inside the function, replacing console.log with a dedicated logging library becomes a question of a major rewrite. Similarly, the validation is now included into the add function itself, which might be an overkill for every function (see my blog post paranoid coding).

My typical rule of thumb to determine tight coupling is to say aloud what the function does and listen for conjunction AND:

add logs its arguments AND validates them AND returns their sum

If I hear even a single AND, the function's purpose is muddled. More than a single AND makes a function or module a prime candidate for refactoring.

Tightly-coupled refactoring

I sometimes see the following tightly-coupled refactoring

1
2
3
4
5
6
7
8
9
10
11
12
13
function add(a, b) {
return logAdd(a, b);
}
function logAdd(a, b) {
console.log('adding', a, 'to', b);
return numbersAdd(a, b);
}
function numbersAdd(a, b) {
console.assert(typeof a === 'number', 'first argument ' + a + ' is not a number');
console.assert(typeof b === 'number', 'second argument ' + b + ' is not a number');
return _add(a, b);
}
function _add(a, b) { return a + b; }

Each function has a clear goal, but they are very tightly coupled and each feature (logging, validation) cannot be reused with any other function at all!

Progressive enhancement via function composition

A much better approach in this case is to create new functions by combining the original add with separate reusable function that does logging, then with function that does argument validation. Multiple libraries provide this simple functionality, for example underscore-contrib juxt provides a left to right pipeline, calling each given function and returning results as an array. With a little bit of argument and results massaging we can combine invidual functions into the desired complete function with minimal coupling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// each function is completely independent
function _add(a, b) { return a + b; }
function _log(a, b) { console.log('adding', a, 'to', b); }
function _numbers(a, b) {
console.assert(typeof a === 'number', 'first argument ' + a + ' is not a number');
console.assert(typeof b === 'number', 'second argument ' + b + ' is not a number');
}
function lastReturnedElement(fn) {
return function() {
var results = fn.apply(null, [].slice.call(arguments, 0));
return results[results.length - 1];
}
}
var _ = require('underscore-contrib');
var add = lastReturnedElement(_.juxt(_log, _numbers, _add)); // 1
console.log(add(2, 3));

The functions _log, _numbers could be made even more reusable now (by handling varied number of arguments for example). The only place where the individual functions are coupled is in the // 1 line that creates the final add function.

Still, this approach has its limitations:

  • the product add has lost all sense of its individual functions. For example, we cannot change the order of logging and validation, or get to the original _add.
  • we had to write additional code just to get arguments and results in the right order.

Progressive enhancement by adding flavours

Reginald Braithwaite in his new JavaScript Spessore book (due 2014) proposes a tiny flavourize function that adds before and after decorators to a given function. The decorators are kept as the properties of the original function (functions are objects too and you can add properties to them).

The same addition logic using flavours:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var add = flavourize(function (a, b) { return a + b; });
function _log(a, b) { console.log('adding', a, 'to', b); }
function _numbers(a, b) {
console.assert(typeof a === 'number', 'first argument ' + a + ' is not a number');
console.assert(typeof b === 'number', 'second argument ' + b + ' is not a number');
}
add.unshift(_numbers);
add.unshift(_log);
console.log(add);
/* prints
{ [Function: flavoured]
befores: [ [Function: _log], [Function: _numbers] ],
body: [Function],
afters: [] } */
console.log(add(2, 3));
// adding 2 to 3
// 5
console.log(add(2, '3'));
// adding 2 to 3
// AssertionError: second argument 3 is not a number

The syntax unshift (and its mirror push that adds a flavour to run AFTER function) are parallel to methods Array.unshift and Array.push that add new items to Array's start and end respectively.

Decorating via source rewriting

There is one more way to add functional decorators without modifying add - rewrite its source on file load (via Nodejs require hook for example). The transformation looks for function add and inserts extra statements before evaluating / compiling the JavaScript. See examples of source rewriting using abstract syntax trees (AST), esprima and falafel in this blog post by Toby Ho. This is definitely a very very extreme way of adding functionality to a piece of code, and I would recommend against it due to its complexity and obfuscation.

Conclusion

Imagine the future: tiny functions with clear purpose, combined in multiple ways to quickly achieve the desired functionality. Easy to use, simple to test, pleasing to look at. We just need to find ways to combine the functions without making the combination process itself a source of problems. flavourize seems to be a useful little function I am adding to my utility belt.