[plt-scheme] Introducing an Identifier into the Lexical Context of a Macro Cal l

From: Jens Axel Søgaard (jensaxel at soegaard.net)
Date: Tue Oct 12 18:20:27 EDT 2004

Yikes. Something went wrong. Here is a second attempt.


Williams, M. Douglas wrote:
> The PLT Scheme Reference Manual contains the following example in 
> section 12.2.1:

> ---
>
> Another reason to use syntax-case is to implement ``non-hygienic'' macros
> that introduce capturing identifiers: 
>
> (define-syntax (if-it stx)
>   (syntax-case stx ()
>     [(src-if-it test then else)
>      (syntax-case (datum->syntax-object (syntax src-if-it) 'it) ()
>        [it (syntax (let ([it test]) (if it then else)))])])))
> (if-it (memq 'b '(a b c)) it 'nope) ; => '(b c)
>
> ---
>
> Is this the best way to introduce an identifier ('it' in this case) into the
> lexical context of the macro's use? 


Later in the manual there is an example I like better:

  (define-syntax (if-it stx)
    (syntax-case stx ()
      [(src-if-it test then else)
       (with-syntax ([it (datum->syntax-object (syntax src-if-it) 'it)])
         (syntax (let ([it test]) (if it then else))))])))

Right after that example comes:

--- quote start ---

Macros that expand to non-hygienic macros rarely work as intended. For
example:

(define-syntax (cond-it stx)
  (syntax-case stx ()
    [(_ (test body) . rest)
     (syntax (if-it test body (cond-it . rest)))]
    [(_) (syntax (void))]))
(cond-it [(memq 'b '(a b c)) it] [#t 'nope]) ; => undefined variable it

The problem is that cond-it introduces if-it (hygienically), so
cond-it effectively introduces it (hygienically), which doesn't bind
it in the source use of cond-it. In general, the solution is to avoid
macros that expand to uses of non-hygienic macros. (32)

(32) In this particular case, Shriram Krishnamurthi points out
changing if-it to use (datum->syntax-object (syntax test) 'it) solves
the problem in a sensible way.

--- quote ends ---

Suppose for arguments sake that if-it were provided by an external
module, and that you couldn't change it. How should cond-it be
defined?

The only working solution I could find were:

  (define-syntax (cond-it stx)
    (syntax-case stx ()
      [(src-cond-it (test body) . rest)
       (let ((if-it-transformer (syntax-local-value #'if-it))
             (if-it-stx         (datum->syntax-object (syntax src-cond-it) 'if-it)))
         (if-it-transformer #`(#,if-it-stx test body (cond-it . rest))))]
      [(_) (syntax (void))]))

Is there a simpler solution?


Here is the entire example with tests (that breaks with all the simpler
attempts I tried): 


(module if-it mzscheme
  (provide if-it)

  (define-syntax (if-it stx)
    (syntax-case stx ()
      [(src-if-it test then else)
       (with-syntax ([it (datum->syntax-object (syntax src-if-it) 'it)])
         (syntax (let ([it test]) (if it then else))))])))

(module cond-it mzscheme
  (provide cond-it)
  (require if-it)
  ;(require-for-syntax if-it)

  (define-syntax (cond-it stx)
    (syntax-case stx ()
      [(src-cond-it (test body) . rest)
       (let ((if-it-transformer (syntax-local-value #'if-it))
             (if-it-stx         (datum->syntax-object (syntax src-cond-it) 'if-it)))
         (if-it-transformer #`(#,if-it-stx test body (cond-it . rest))))]
      [(_) (syntax (void))])))

(require cond-it)

(cond-it
 [#t it]
 [#t 'nope])

(let ((it 42))
  (cond-it
   [#t it]
   [#t 'nope]))

(let ((if-it 43))
  (let ((it 42))
    (cond-it
     [#t it]
     [#t 'nope])))

(cond-it ((memq 'b '(a b c))                                                  
            (let ((it0 it))
              (if-it (memq 'y '(x y z))
                     (values it0 it)
                     'nope0)))               
           (#t 'nope1))

(require if-it)

(cond-it
 [#t it]
 [#t 'nope])

(let ((it 42))
  (cond-it
   [#t it]
   [#t 'nope]))

(let ((if-it 43))
  (let ((it 42))
    (cond-it
     [#t it]
     [#t 'nope])))

(cond-it ((memq 'b '(a b c))                                                  
            (let ((it0 it))
              (if-it (memq 'y '(x y z)) (values it0 it) 'nope0)))               
           (#t 'nope1))


-- 
Jens Axel Søgaard





Posted on the users mailing list.