Skip to content

Instantly share code, notes, and snippets.

@artisonian
Last active September 4, 2015 01:08
Show Gist options
  • Save artisonian/a33ba5d6d1aa808ba02d to your computer and use it in GitHub Desktop.
Save artisonian/a33ba5d6d1aa808ba02d to your computer and use it in GitHub Desktop.
Self-Referential Functions

Self-referential functions in JavaScript

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));
}

Our First Option

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); // => 3

Temporary Values

Now, 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); // => 3

This 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); // => 3

Closing Thoughts

The 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment