Rob Pike describes a way of encoding options using functions. Let's see if we can do something similar in JavaScript.
NOTE: To execute this document as code, try erudite.
We'll need a "Foo" type to follow along with the blog post. We'll use a constructor:
function Foo () {}
Foo.prototype.doSomething = function () {
console.log('Foo', this);
}First, we'll define option "type". In JavaScript, this is merely a function.
var option = function (foo) { /* ...snip... */ };Let's add a function to Foo's prototype which sets options. Notice this is essentially the plugin pattern used extensively by Segment:
Foo.prototype.option = function (...options) {
options.forEach(opt => opt(this));
}Here's an option to set verbosity on Foo:
var verbosity = function (level) {
return function (foo) {
foo.verbosity = level;
};
};If you squint, this looks like Express middleware, except it doesn't allow chaining (but it's simple enough to do...just return this at the end of the method).
Here's an example of it in action:
var foo = new Foo();
foo.option(verbosity(3));
console.log(foo.verbosity); // => 3Now, things differ from what's you normally see in server-side JavaScript. We want to be able to set an option temporarily, then restore it later.
We'll change verbosity to return the previous value when called:
verbosity = function (level) {
return function (foo) {
var previous = foo.verbosity;
foo.verbosity = level;
return previous;
}
}Next, we'll change Foo#options to return the previous value of the last argument:
Foo.prototype.option = function (...options) {
return options.reduce((prev, opt) => {
return opt(this);
}, null);
}Let's test this new version:
var previousVerbosity = foo.option(verbosity(5));
foo.doSomething();
foo.option(verbosity(previousVerbosity));
console.log(foo.verbosity); // => 3This works, but the API could be nicer. Let's return the previous value of verbosity as a function instead of its value:
verbosity = function verbosityPlugin (level) {
return function (foo) {
var previous = foo.verbosity;
foo.verbosity = level;
return verbosityPlugin(previous);
}
}Pike mentions this pattern (self-referential function types) is akin too a state machine he presented in his talk, Lexical Scanning in Go. Each state is a function that returns another state function (or null if the state is terminal).
Does it work? Yes.
previousVerbosity = foo.option(verbosity(1));
foo.doSomething();
foo.option(previousVerbosity);
console.log(foo.verbosity); // => 3The plugin pattern works well to model option types in JavaScript. With variations to the return type, plugins can be middleware for web servers, steps in a sequence of tasks, and more.