Macros in JavaScript please

JavaScript doesn't have macros. Various forms of macros have been around for decades in other languages and JavaScript programmers have to struggle along immersed in verbosity without them.

an unless macro

Always start with a simple example: an unless macro is a classic first macro. Suppose we are really tired of writing this

if (!(conditional)) {
  statements
}

If we had macros we could extend the syntax of JavaScript itself by defining an unless macro and we could just write

unless (conditional) {
  statements
}

When JavaScript encounters "unless" in the syntax tree it could expand the macro to the usual negated if statement above. That is, if we had macros in JavaScript.

Currently we have a few choices for writing an unless statement.

Solution 1

Use a text editor snippet. When you type "unless" and press space or tab, the editor expands the unless into

if (!()) {

}

and puts your cursor between the inner parens for you to start typing your conditional. For more complex macros, this makes for bulky code to read and it must all be downloaded by the browser, in the case of browser scripting.

Solution 2

unless(conditional, function() {
    statements;
});

where unless is a function

var unless = function(conditional, statements) {
  if (!(conditional)) {
    return statements();
  }
}

This solution is bulky in this case because of the overhead of the function literals. This solution is easy to debug as JavaScript will report the location of errors intelligibly. I used bits of code like this solution (not for unless, of course!) and it feels like sending functions into a mini framework.

Solution 3

eval(unless("conditional", "statements"))

where unless is

var unless = function(conditional, statements) {
return 'if (!('+conditional+')) {'+
          statements + 
       '}';
}

This almost looks like a macro. Escaping all the quotation marks would be a pain. Having eval run frequently would need avoiding for better performance.

Solution 4

unless (conditional) {
  statements
}

Wow! Looks pretty good. Unfortunately we need to have a server-side program compile this down to JavaScript before serving it to the browser. This is basically the concept behind GWT and many other x-to-JavaScript compilers.

This server-side compiler system is difficult to build and the generated code is difficult to debug. A map from generated code line numbers to source line numbers would help find errors in the source code.

A function wrapping macro

Here is a more practical example. In another article, I wrote about a pattern I use to wrap functions. The pattern looks like this example of adding debugging output to an event library function.

LIB.on = (function() {
  var original = LIB.on;
  return function(element, type, callback) {
    console.log('adding ' + type + ' listener');
    return original(element, type, callback);
  };
})();

If JavaScript had macros we could define a macro wrapBefore and write something short like

wrapBefore(LIB, on, function(element, type, callback) {
  console.log('adding ' + type + ' listener');
});

which would expand to something like

LIB.on = (function() {
  var original = LIB.on;
  return function() {
    (function(element, type, callback) {
      console.log('adding ' + type + ' listener');
    }).apply(null, arguments);
    return original.apply(LIB, arguments);
  };
})();

This wrapping pattern is not something that can be extracted elegantly into a helper function because it involves establishing a closure or passing a reference of the original function into the wrapping function (example).

Conclusion

It would be very helpful if we had macros right in the JavaScript language. The standard ECMAScript language could remain small and developers could add all the domain-specific syntax they want. Versions of ECMAScript after the version with macros would not be necessary (except perhaps to include some common macros in the language for performance.) Fingers crossed for ECMAScript 5th edition.

Comments

Have something to write? Comment on this article.

Andrew Hedges April 29, 2008

The problem with waiting until ECMAScript 5 is that version 4 is going to quadruple the syntax of the language. That syntax will not go away if macros are introduced, so in any case it looks like we're doomed to having a bloated, kitchen sink scripting language going forward.

David April 30, 2008

I'm not sure I understand what you are complaining about?

If someone coding JavaScript can't figure out this line, they need to go back to the books for a bit longer.

if(!isPurple){
  //do the non-purple stuff...
}

What gets ugly in JavaScript, is when you have multple and/or conditions that you are trying to meet.

if(a == b || b > 14 && c < a || b != d){
  //...
}

This becomes unreadable, and I have no objection to defining a variable that helps indicate the condition.

var isValidObj = false;
if(cond_x) isValidObj = true;
if(cond_y) isValidObj = true;
if(cond_z) isValidObj = true;

then the actual if statement...

if(isValidObj){
  //do stuff here...
}

As for the original post topic, "macros", what exactly are you after? I would take the power of JavaScript over what is usually limited functionality of macros any day.

Jani Hartikainen April 30, 2008

In my opinion this is a two-sided sword: Macros can provide some nice uses as you highlighted, but on the other hand, they can make the code a maintenance-hell.

You could easily think "Hey, this is a cool macro!" and go totally overboard, making the code difficult to understand because of the new syntax you made up that no one else has seen before.

Peter Michaux April 30, 2008

Jani,

Yes macros could make a mess of things if used poorly...but so can closures, mutable objects, global variables, etc. Powerful languages require programming with good taste.

Peter Michaux May 3, 2008

David,

I've updated the article to make it more clear that unless is simply an example macro. I'm not sure what you mean by the "usually limited functionality of macros". To me they seem to be quite the opposite. They allow the developer to extend the language's syntax!

Dan Evans May 5, 2008

I realize that the unless example is not really the point of the article, but what's wrong with writing example 2 as:

unless(conditional, function(){
    statements;
});

where unless is

var unless = function(conditional, statements) {
  if (!conditional) {
    return statements();
  }
}

Maybe that is not your indentation of choice, to me it is most similar to how the if syntax would normally look. My question mainly arises because wrapping the conditional variable in a function literal is what makes this solution look clunky to me. Why is it necessary? (I tested it briefly in the firebug console and it seems to work without the function literal just fine...)

Peter Michaux May 5, 2008

Dan,

I think some of the real-world examples I have in code that make me want macros are why I wrapped the conditional in a function. You are right that your code is cleaner and I've updated the article to use your code. Thanks.

rabidsnail January 26, 2009

Problem:

If you can extend the syntax of javascript then you can make languages other than javascript parseable. For example, you could write a macro to rewrite <foo><bar>baz</bar></baz> as foo(bar(baz)). Then you could get around the single origin policy by loading xml with <script> tags. That would totally break the security model.

Luc Traonmilin August 25, 2009

you can also do:

function unless(condition) {
   return function() {
       if (!condition) {
           arguments[0]();
       }
   }
}

and call it that way:

unless(false)(function() { alert("ok"); });
Red Daly August 4, 2010

You could always just used Parenscript, which has lisp-style macros and compiles to Javascript. This is my preferred option.

You could also introduce a pre-processing step for Javascript to define macros. The problem that most non-lisp languages encounter with macro definition is handling a complex syntax tree in the macro-processing stage. In lisp, the syntax tree is mostly list processing.

Have something to write? Comment on this article.