n01senet

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)))
 
Comments:
FWIW, you probably already know this, but with gcc the C code could be implemented as:

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

Not so bad, really, but I'm sure you could find a worse example.

Another way would be to use gcc's typeof (not necessary here, but perhaps it would be needed for a different example):

#define my_or(a, b) ({ typeof (a) _a = (a); _a ? _a : (b); })
 
It sounds like what you are asking for is lazy evaluation, not macros. In which case, you want something like Haskell or Clean.

Once you have lazy evaluation, the compile time/run time distinction is decreased.
 
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.
[end quote]

I'm not entirely sure, but I think in C you could instead pass my_or a function pointer instead of the value of the function.
QString x = my_or(y, &calcDefault());

my_or(y, (*cD())) {
return y.isEmpty() ? cD() : y;
}

I'm writing this dry lab, and am probably missing a * somewhere, but I would think that would work.
 
You don't need macros for this, you need a clean syntax for anonymous functions or lazy evaluation. Smalltalk implements all it's control structures with anonymous functions. For example...

^number isEven or: [number isPrime]

Same with and, no need for macros. The place where macros are necessary, is when you want to generate lots of boiler plate code like accessors, or introduce new variable bindings into a form like the anaphoric if where "it" refers to the result of the test condition.
 
acess to parse tree:
boo.

macros, access to parse tree, and frakking badass language. bamboo is my hero.
 
In Perl you would just use ||. Quick test of this:

perl -le '$a = 1; sub a { $a++ }; $b = 5 || a(); print $a;print $b'

Prints 1 and 5.

perl -le '$a = 1; sub a { $a++ }; $b = 0 || a(); print $a;print $b'

Prints 2 and 1.

Actually || works the same in Javascript too. But yeah, Macros are cool.
 
The commenters above are missing the point. Lazy evaluation / short circuiting / closure creation is just an example of what can be made with macros.
 
True. No doubt I love macros, it was just a challenge to do lazy eval without it.

I often use them for compressing array coordinates so a function can return a location in a multidimensional array in an int instead of using a struct. Obviously you're limited by the number you multiply by in the macro, but for small 2D arrays it works.

#define compress(a,b) (a*1000 + b)
#define uncompress(a) a/1000][a%1000
// note the missing outside [ ] so they're used in the code to make it more clear what's going on.

int findlocation() {
return compress(3, 4)
}

arrayname[uncompress(findlocation())]
 
Post a Comment



<< Home
A community blog by the members of n01se.net

ARCHIVES
May 2006 / June 2006 / July 2006 / October 2006 / February 2007 / May 2007 / July 2007 / February 2008 / March 2008 / May 2008 /


Powered by Blogger