[plt-scheme] Applying DRY in macros

From: Jens Axel Soegaard (jensaxel at soegaard.net)
Date: Sat Sep 13 18:26:04 EDT 2008

danprager at optusnet.com.au wrote:
> Thanks Jens.  I was wondering whether syntax-case -- which I haven't yet come to grips with -- might hold some clues to writing macros more briefly and clearly, so your rewrite is welcome:
>> Here is an alternative:
>> (define-syntax (for stx)
>>   (define (template var list-expr bodies)
>>     #`(map (lambda (#,var) #, at bodies) #,list-expr))
>>   (syntax-case stx (in as)
>>     [(for element in list body ...)
>>      (template #'element #'list #'(body ...))]
>>     [(for list as element body ...)
>>      (template #'element #'list #'(body ...))]))
> In this instance, however, it looks to me as if the amount of duplication has remained the same, 
The game as I understood it, was to write the macro with the use of only 
one template.
In the solution above the only template is #`(map (lambda (#,var) 
#, at bodies) #,list-expr) .
> while the apparent complexity -- #`, #,@, #'  -- has increased.  
My solution was meant to illustrate how to implement your magic syntax-rules
solution. Therefore you should compare agains that.
> Compare with the earlier:
> (define-syntax for
>   (syntax-rules (in as)
>     ((for element in list body ...)
>      (map (lambda (element)
>             body ...)
>           list))
>     ((for list as element body ...)
>      (for element in list body ...))))
> which I was hoping could be improved on.
And later you asked:

 > What I would _like_ to be able to write is something like:

> (define-syntax for
>  (syntax-rules-with-**magic** (in as)
>    (**magic** ([template
>                 (map (lambda (element)
>                        body ...)
>                      list)]))
>    ((for element in list body ...) template)
>    ((for element in list body ...) template)))

so my solution should be seen as an implementation
of the magic solution.

Let's examine the magic solution, and imagine
the syntax expander must expand
  (for x in (list 1 2 3) (display x)).
First the expander compares the for-expression
agains the patterns on the left hand side, until
it finds a match. Then it binds the pattern variables
with the pieces of syntax they matched in the
for-expression. In this case element is bound to x,
and body ... is bound to (display x).

In a normal syntax-rules expander the righthand side
must be a template. The expander would therefore
substitute occurences of the pattern variables element
and body ... in the template.

In the magic syntax-rules solution the righthand side
is a "template variable" bound to the template
(map (lambda (element) body ...). Thus some mechanism
is needed to lookup template. Also since the definition
of template happens before the pattern variables come
into scope, there need to be a way to transfer the
values of the pattern variables in order to make
the substitution. 

I now claim that the syntax-case solution implements
the what the magic solution does. The difference is
that the transfer is explicit.

A function called template is to used to
transfer the values of the pattern variables 
and make the substitution. The function receives 
the values of the pattern variables and perform 
the substitution:

  (define (template var list-expr bodies)
    #`(map (lambda (#,var) #, at bodies) #,list-expr))

If you prefer one can avoid the line noise and use 
with-syntax (The difference is purely cosmetic though).

(define (template var list-expr bodies)
    ([var var] [list-expr list-expr] [(body ...) bodies])
    (syntax (map (lambda (var) body ...) list-expr)))

Then in the righthand side in the syntax-case macro
  (syntax-case stx (in as)
    [(for element in list body ...)
     (template #'element #'list #'(body ...))]

nothing happens but the transferal of the values of 
the pattern variables. The lookup of the "template
variable" template is replace with a normal lookup
of a function.

Conclusion: It wouldn't be too difficult to implement
the magic-syntax-rules.

But back to this:
(define-syntax for
  (syntax-rules (in as)
    ((for element in list body ...)
     (map (lambda (element) body ...) list))
    ((for list as element body ...)
     (for element in list body ...))))

I don't consider the two occurences of
(for element in list body ...) for duplication
of code.

The first occurrence defines what 
(for element in list body ...) means.
Thus the first occurence does not count
as a use of a for-expression.

The second occurence is a use of the 
new language constract

Compare the situation with the
definition of a recursive function:

 (define (loop n)
   (display "*")
   (loop n))

Here (loop n) isn't duplicated code.
The first (loop n) defines what the
function loop.

The second is a use of the newly defined function.

Jens Axel Søgaard

Posted on the users mailing list.