[racket-dev] When is it safe to not rename a runtime value in a macro?

From: Ryan Culpepper (ryan at cs.utah.edu)
Date: Sat Aug 25 15:56:38 EDT 2012

On 08/25/2012 01:57 PM, Neil Toronto wrote:
> On 08/25/2012 11:33 AM, Ryan Culpepper wrote:
>> On 08/25/2012 01:08 PM, Neil Toronto wrote:
>>> A number can expand to an arbitrary expression? How?
>>>
>>> And what do you mean by "the '#%datum' macro associated with them"?
>>> Applied to them?
>>
>>  > (let-syntax ([#%datum (lambda (stx) #'(printf "hello\n"))]) 5)
>> hello
>>
>> In Racket, literal data carry lexical information just like identifiers.
>> When a literal datum is used as an expression, the macro expander
>> synthesizes a '#%datum' identifier that determines what to do with the
>> literal. The Racket '#%datum' macro just expands into a 'quote'
>> expression if the datum is not a keyword.
>>
>> The implicit '#%app' syntax works similarly, except it takes its lexical
>> context from the pair that represents the application.
>
> Holy heck I had no idea. That's awesome and scary.
>
> So this is what I have now:
>
> (define skip-ids
>    (syntax->list #'(+ - * / < > <= >= = min max)))
>
> (define (skip-binding? e-stx)
>    (let ([e-stx  (local-expand e-stx 'expression #f)])
>      (and (identifier? e-stx)
>           (findf (λ (skip-id) (free-identifier=? skip-id e-stx))
>                  skip-ids))))
>
>
> I would have called it safe before today, but I knew fewer awesome and
> scary things. Does it look safe to you?

In this case you can get rid of the 'local-expand', since you're only 
looking for references to particular variables.

If you do keep the 'local-expand', you should use the result in the code 
your macro produces so that the expression isn't expanded multiple 
times. For example:

(define-syntax (m stx)
   (syntax-case stx ()
     [(m e stuff ...)
      (let ([ee (local-expand #'e 'expression #f)])
        (if (and (identifier? ee)
                 (for/or ([skip-id skip-ids])
                   (free-identifier=? ee skip-id)))
            #`(real-m #,ee stuff ...)
            #`(let ([tmp #,ee])
                (real-m tmp stuff ...))))]))

Depending on exactly what you're doing, you might want an empty stop 
list instead of #f. Or you might want to use 
'syntax-local-expand-expression' instead so that the expander doesn't 
have to retraverse the expanded expression.

--

In other words, a macro's subexpressions should be used linearly (as in 
"linear types"). Putting an expression in the macro's result in an 
expression context counts as a use. (But putting it in the result in a 
quoted context doesn't.) Calling 'local-expand' or 
'syntax-local-expand-expression' on it also counts as a use, but it 
gives back another linearly-restricted expression. (The same applies to 
definitions; I should really say all "forms" should be used linearly.)

Ryan


Posted on the dev mailing list.