Wednesday, May 06, 2009

Syntactic Macros in Groovy

In CLisp, macros are defined by quoting symbols and lists with backquote `, and escaping from them with comma , or comma-at ,@, perhaps nestedly so. One day I realized this syntax is similar to GStrings in Groovy, which are quoted with double-quote " and escaped with dollar $, perhaps nestedly so. Lisp symbols are similar to Java interned strings. I wondered if GString syntax be used to specify syntactic macros in Groovy.

In Paul Graham's online book, On Lisp, he describes how to write a macro:
(defmacro nil! (var) `(setq ,var nil))
Using GString notation, we could define an equivalent macro in Groovy by writing:
defMacro("setq($var, null)"){"nilBang($var)"}

For Groovy to match Lisp in functionality, we would need two more functions:
'[sum, a, b]'.asFunc() == 'sum(a, b)'
'[+, a, b]'.asFunc() == '(a + b)'
and:
"[a, $b, c]".expand() == [a, 2, c]
as well as the already-provided:
"[$a, $b]".evaluate() == 3

Paul Graham writes that when using the backquote in Lisp:
`(a b c) is the same as '(a b c)
and `(a b c) is the same as (list 'a 'b 'c)
With Groovy syntactic macros:
"[a, b, c]" will be the same as '[a, b, c]'
and "[a, b, c]" the same as ["a", "b", "c"].

Using the comma with the backquote in Lisp:
`(a ,b c ,d) is the same as (list 'a b 'c d)
With Groovy macros:
"[a, $b, c, $d]" will be the same as ["a", b, "c", d]

In Lisp, if we assign some values using (setq a 1 b 2 c 3), then we'll get:
> `(a ,b c)
(A 2 C)

In Groovy, if we assign using def a=1, b=2, c=3, then we would get:
assert "[a, $b, c]".expand() == [a, 2, c]

For an example with nesting using these values:
> `(a (,b c))
(A (2 C))

In Groovy it would be:
assert "[a, [$b, c]]".expand() == [a, [2, c]]

A more complex example from On Lisp:
> `(a b ,c (',(+ a b c)) (+ a b) 'c '((,a ,b)))
(A B 3 ('6) (+ A B) 'C '((1 2)))

In Groovy it would be:
assert "[a, b, $c, ['${"[+, a, b, c]".asFunc()}'], " +
       "[+, a, b], 'c', '[[$a, $b]]']".expand() ==
  [a, b, 3, ['6'], [+, a, b], 'c', '[[1, 2]]']


Another example, in Lisp:
`(,a ,(b `,c))
and in Groovy:
"[$a, ${[b, "$c"]}]"

We'd need to introduce some additional syntax to match the comma-at notation from Lisp:
> (setq b ’(1 2 3))
(1 2 3)
> `(a ,b c)
(A (1 2 3) C)
> `(a ,@b c)
(A 1 2 3 C)


The Groovy equivalent, with additional syntax $* to show interpolate-with-spreading, would be:
def b= [1, 2, 3]
"[a, $b, c]".expand == [a, [1, 2, 3], c]
"[a, $*b, c]".expand == [a, 1, 2, 3, c]


I'm nowhere near providing this sort of functionality in Groovy/DLR, but such self-referential syntactic macros would complement the recently added AST macro system in Groovy 1.6.

2 comments:

Hamlet D'Arcy said...

Have you seen the slides for my Groovy AST transforms talk? http://is.gd/XdUR

It covers this exact example (nil!) and talks about what can be done. After the work with GEP-2 (http://docs.codehaus.org/display/GroovyJSR/GEP+2+-+AST+Builder+Support) is done in a few weeks I plan on taking a look at macros. However, I think the Boo metamethod approach is a more promising parallel for Groovy than Lisp macros. A metamethod takes as parameters Expressions and always returns a List<Expression>. A transform handles converting calling code into a macro call and back into code. SO nil!/setNull could be this (with no language syntax change needed):

@Macro
def setNull(Expression e) {
// write AST to set the VariableExpression e to null
}

// call code
def x = 5
setNull(x)
assert 5 == null

Hamlet D'Arcy said...

sorry, last line should be:
assert x == null