Tuesday, December 11, 2007

Anaphoric macros

Jens G. writes in with a useful programming mechanism usually found in functional languages. Say you have a semipredicate - a block of code that returns a logical false if the code fails, but an actual return value if it succeeds. Opening a file is a good example. You'd like to return the file handle if you got it, but do something else if you didn't, like raise an exception.

Naively, you could do:

if (semipredicate()) {return semipredicate();}
else {....}

but that requires two calls to semipredicate(), which could be expensive. Of course, you could bind the result to a local variable:

foo = semipredicate();
if (foo) {return foo;}
else {...}

Local variables are messy and inelegant, though, and what's a functional language if not elegant? In C-style code, you could use

return (semipredicate() || somethingelse() );

but then you aren't able to change the return value, which would be a very handy thing indeed. Enter anaphoric macros. An anaphor, as Jens explains, is a linguistic term used to refer to something said previously. Pronouns in English are anaphors.

Enter LISP and all its parenthetical goodness. Basically, we define a macro that captures the result of the semipredicate, and that result can be manipulated in the source code. Here's the definition for anaphoric-if:

(defmacro aif (condition consequence &optional (alternative nil))
`(let ((it ,condition))
(if it ,consequence ,alternative)))

The backquote (`) character lets the LISP interpreter know to only evaluate the bits of code inside the macro that are marked with a comma. So in this macro, "it" is bound to the result of condition, and then we evaluate consequence if condition returned something other than NIL, and alternative otherwise. You can use this macro in the source like so:

(aif (predicate ...) it (something_else ...))

Notice how you can refer to "it" like it's a free variable, but when the macro is expanded it will actually refer to the result of predicate! This allows for neat things like being able to return a modified value of it:

(aif (predicate ...) (* it 2) (something_else ...))

There are also anaphoric versions of while, do, etc. Visit the On Lisp page to learn more.

2 comments:

Tordek said...

Sort of cool, but in Lisp "or" returns the first non-nil value of the expressions passed to it:

(if (op)
(op)
(otherstuff))

can be written as
(or (op)
(otherstuff))

as long as (op) has been defined to return nil on false (as per default).

Jabberwockey said...

That's not the point :)

The anaphoric macros can be used if you need the value for a different equation.

For instance: if you would use

(or (* 2 (op)) (otherstuff))

it wouldn't work when op returns nil - nil is not a number. With the anaphoric macro however...

(aif (op) (* 2 it) (otherstuff))

...and it works :)