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.
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.
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.
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.
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!
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...)
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.
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.
you can also do:
function unless(condition) {
return function() {
if (!condition) {
arguments[0]();
}
}
}
and call it that way:
unless(false)(function() { alert("ok"); });
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.
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.