Configurable function pattern

A function can configure itself via options argument to keep API clean and simple.

I love writing tiny JavaScript modules that do just a single thing. I even started writing modules that export a single function. This makes it extremely simple to understand the module and start using it by just looking at a small documented example (I recommend using xplain to document javascript code). But what if my function needs to be configured? Let us take quote as an example. It exports single function that can safely add quotes to a given string, without double-quoting it

1
2
3
var quote = require('quote');
quote('foo'); // "foo"
quote(quote('foo')): // "foo"

What if a user wants to use single quote character instead of double quotes character? I could approach this problem in 3 different ways

1 - pass options object with each call

I always advocate using an options object instead of passing individual arguments: this makes the API a lot more robust to changing options. In this case, the options object would contains single property. The user could pass options object with each call.

1
quote('foo', { quotes: '\'' }); // 'foo'

In this case, the user most likely would always pass the same options object with each call. Because I advocate placing options least likely to change first in the function signature, I would actually make the API take the options object first. This makes it very convenient to do partial application

1
2
3
var options = { quotes: '\'' };
var quote = require('quote').bind(null, options);
quote('foo'); // 'foo'

2 - use global options method

Passing same info with each call seems weird, especially if the defaults are working just fine. Thus I could create a separate config function and attach it to the returned quote function. The config would accept options object and would store these options as global properties.

1
2
3
var quote = require('quotes');
quote.config({ quotes: '\'' });
quote('foo'); // 'foo'

The problem with this approach is subtle, but common to every shared data situation. We might set the options several times, and the last set of options always wins!

1
2
3
4
5
6
7
8
9
10
11
quote.config({ quotes: '\'' });
function foo() {
quote('foo'); // expecting 'foo'
}
// somewhere later
quote.config({ quotes: '|' });
function bar() {
quote('bar'); // expecting |bar|
}
foo(); // |foo|
bar(); // |bar|

3 - configure the local function closure

The typical argument to the function is string; I could detect if an object was passed in and assume it to be the options. In that case, I would return a new function that uses options from that options object.

First the top level interface:

1
2
3
4
5
6
7
8
function quote(str) {
if (typeof str === 'object') {
// configure and return new quote function
return quoteOptions.bind(null, str);
}
return quoteOptions(str);
}
module.exports = quote; // 1

Second, the actual worker that takes options object and string (just like method 1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function quoteOptions(options, str) {
if (arguments.length === 1) {
// use defaults
str = options;
options = { quotes: '"' };
}
if (typeof str === 'string') {
// actual worker function
return quoteString(options, str);
}
return str;
}
function quoteString(options, str) {
if (str === '') {
return options.quotes + options.quotes;
}
...
}

Using this approach you can configure and use multiple functions in parallel

1
2
3
4
5
var wildcards = require('quote')({ quotes: '*' });
var ups = require('quote')({ quotes: '^' });
wildcards === ups; // false, these are different functions
wildcards('foo'); // *foo*
ups('bar'); // ^ups^

I kept the top exported function quote (// 1) simple, it could be combined with quoteOptions to allow multiple configuration, but in this case it would only make the code more complex, and I do not see the case for the following API use:

1
2
3
4
var quote = require('quote')({ quotes: '*' });
quote('foo'); // *foo*
quote = quote({ quotes: '"' });
quote('bar'); // "bar"