Searching for Signal

the n01se blog

Why you want macros (even if you don’t know it yet)

This is a slightly edited transcript of a conversation with my friend Brian.

Let's say you want to write your own "or" function. Let's say you're not satisfied with C++'s || because you want the actual value that is true, not just the boolean "true". You want to be able to say:

QString x = y || "default";

And to make things easy, let's not worry about the goofy operator syntax. You'd be happy with:

QString x = my_or( y, "default" );

Now, you could write that, right? Let's assume it only works on QStrings too, just to make things easy:

QString my_or( const QString& a, const QString& b ) {
    return a.isEmpty() ? b : a;
}

But what if b is a big ol' calculation:

QString x = my_or( y, calculateDefault() );

You want to short-circuit, and only call calculateDefault() if y is false. In C++ you're screwed. Can't do it.

Also screwed in Java, C# (as far as I know), perl, ruby, etc. You need macros, or something similar. [See below for what I learned about C#]

Now, there are half measures that will let you squeak by in this very simple case. You can in fact use C pre-processor macros. Gross, but you can:

#define my_or( a, b )  ((a)?(a):(b))

Oops, I just evaluated a twice. In javascript (or perl or ruby) you can use closures:

function my_or( fa, fb ) {
    var a = fa();
    return a ? a : fb();
}

and then call it with this happy formulation:

var x = my_or( function() { return y; },
               function() { return calculateDefault(); } );

ew.

Now if the only reason you want a macro is for delayed evaluation (which is all you need in this case), Scala gives you that one particular feature.

def my_or( a: => String, b: => String ) = {
    if( a ) a; else b;
}

That tricky little => tossed in there makes the argument "lazy" and gives us what we need in this case. But of course there are other things that macros provide that the lazy argument feature does not.

Brian: So you would argue that macros should be part of every modern computer language

oh. Well... Hm, I'd never thought of phrasing it that way.

Having macros makes a language much more flexible and powerful.

Brian: Are macros in other languages like lisp implemented in a pre-processor fashion?

Lisp macros can call any regular lisp function. This a fundamental difference compared to, for example, C pre-proc macros which can't use anything except other pre-proc macros. Usage of lisp macros are expanded before the result is evaluated, so it's "pre" in that sense, but they can use other functions and macros and in that sense they're very much mixed into the evaluation system.

It's interesting to note that Clojure, for example, has no interpreter. It compiles everything into Java bytecode on the fly before it runs it. But it still has full-on lisp macros.

Brian: It would be interesting to see what the Scala folks think about macros

Yeah, I whined about them quite a bit in the Scala IRC channel. Somebody seemed to think someone was working on them. *shrug*

Brian: Runtime macros in C# 3.0

Interesting.

So the two drawbacks of C#'s solution (compared to Clojure) are (1) special syntax for calling a "macro" and (2) expansion happens at runtime. But it's a bit better off than say JavaScript which can't do C++ tricks because it has no pre-processor, and can't do C#'s tricks because it doesn't have access to parse trees.

Brian: That's what you really want -- some sort of access to the parse tree.

Right, access to the parse tree. exactly.

For the record, Clojure's built in or already does what we want, and is in fact implemented as a macro. You can find the real version in Clojure's boot.clj file. That one's already pretty simple, but here's a slightly simplified version that handles just the two-argument examples above:

(defmacro my-or [a b] `(let [av# ~a] (if av# av# ~b)))