With a little help from with

You know the old saying:

with (JavaScript) {
    metaprogramming.is('possible');
}

I’m going to leave the discussion of what constitutes metaprogramming to another day (read: never), but what I will say is that I’m becoming more interested in DSLs and fluent interfaces. I want the code I write to work at a very high level, where it describes what I’m trying to achieve in terms that anyone else (read: me, six months later) can understand. I particularly like this example of a JavaScript DSL for describing state machines:

var Machine = FSM.build(function(fsm) { with (fsm) {
  onUnexpectedEvent(function() { ... });

  state('start', 'initial')
    .event('go')
      .goesTo('middle')
      .doing(function() { ... })
      .doing('phew')
    .event('run')
      .goesTo('finish')
    .onExiting(function() { ... });

  state('middle')
    .onUnexpectedEvent(function() { ... })
    .onEntering(function() { ... })
    .event('back')
      .goesTo('start')
      .onlyIf(function() { return true_or_false })
    .event('go')
      .goesTo('finish');

  state('finish', 'final');
}});

This example, along with various testing/speccing libraries, make heavy use of the much-maligned with statement. with essentially works as follows: inside the with block, all variable names are first looked up in the object passed to with, before being looked up in the parent scope. so:

var object = {
    name: 'James',
    getName: function() {
        return this.name;
    }
};

var theName, name;

with (object) {
    name = 'Bob';
    theName = getName();
}

// theName == "Bob"
// name == ""
// object.name == "Bob"
// object.theName === undefined

Now, clearly this can cause serious headaches if overused, and I would never recommend writing your business logic using with statements. But, for writing descriptions of things — like specs, or state machine definitions, or class definitions — it can be quite helpful. Using JS.Class, you can implement some of Ruby’s class definition syntax in 20 lines of code:

JS.Ruby = (function() {
  
  var extendDSL = function(builder, source) {
    for (var method in source) {
      if (!builder[method] && typeof source[method] == 'function')
        builder[method] = source[method].bind(source);
    }
  };
  
  var ClassBuilder = function(klass) {
    this.def  = klass.method('instanceMethod');
    this.self = {def: klass.method('classMethod')};
    
    extendDSL(this, klass);
    
    this.extend = function(source) {
      klass.extend(source);
      extendDSL(this, klass);
    };
  };
  
  return function(klass, define) {
    define(new ClassBuilder(klass));
  };
})();

That implements a DSL that, with a little help from with, lets you write JavaScript classes that look like Ruby ones, with all the power and flexibility that entails (let’s pretend you’re porting ActiveRecord to JavaScript):

ActiveRecord.Base = JS.Class();
JS.Ruby(ActiveRecord.Base, function(Ruby) { with(Ruby) {

  extend(ActiveRecord.Validations);
  
  validatesPresenceOf('name');
  validatesLengthOf('password', {minimum: 8});
  
  with (self) {
    def('create', function(attrs) {
      var base = new this();
      base.setAttributes(attrs || {});
      return base;
    });
  }
  
  def('initialize', function() {
    this.attributes = {};
  });
  
  def('setAttributes', function(attrs) {
    for (var key in attrs)
      this.attributes[key] = attrs[key];
  });
  
  var methodName = function(string) {
    return string.replace(/_(\w)/g, function(match, s) {
      return s.toUpperCase();
    });
  };
  
  // We're in a function, so any kind of logic goes
  ['name', 'email', 'password'].forEach(function(name) {
    def(methodName(name), function() {
      return this.attributes[name];
    });
    
    def(methodName('set_' + name), function(value) {
      this.attributes[name] = value;
    });
  });
}});

The whole class definition takes place inside a closure, so you can create any helper code you want in there and it won’t touch the global namespace. You have more control over the ordering of method definitions and mixin inclusions, and you can define methods dynamically to your heart’s content. The class behaves as you’d expect:

var me = ActiveRecord.Base.create({name: 'James'});
me.setAttributes({password: 'fizzbuzz'});
me.setEmail('james@jcoglan.com');
me.name() == 'James'  // true

The moral of this story is that with can be useful occasionally, especially when you want to describe or define something without repeating yourself too much. It’s useful for writing DSLs for doing such jobs, but don’t go sprinkling it around inside your actual methods. You’ll just get confused.

If you've enjoyed this article, you might enjoy my recently published book JavaScript Testing Recipes. It's full of simple techniques for writing modular, maintainable JavaScript apps in the browser and on the server.