[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:07:02 EDT 2004

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 another version, which 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|))))]))

The question reminds me about a discussion I had some time ago. After 
the above if-it example, the documentation says:

--- quote starts ---

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/ <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-8.html#%_idx_198> (cond-it stx)
    (syntax-case stx ()
      [(src-cond-it (test body) . rest)
       (/let/ <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_idx_124> ((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 some tests:

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

  (/define-syntax/ <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-8.html#%_idx_198> (if-it stx)
    (syntax-case stx ()
      [(src-if-it test then else <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_idx_108>)
       (/with-syntax/ ([it (datum->syntax-object (syntax src-if-it) 'it)])
         (syntax (/let/ <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_idx_124> ([it test]) (/if/ <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_idx_98> it then else <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_idx_108>))))])))

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

  (/define-syntax/ <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-8.html#%_idx_198> (cond-it stx)
    (syntax-case stx ()
      [(src-cond-it (test body) . rest)
       (/let/ <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_idx_124> ((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/ <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_idx_124> ((it 42))
  (cond-it 
   [#t it] 
   [#t 'nope]))

(/let/ <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_idx_124> ((if-it 43))
  (/let/ <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_idx_124> ((it 42))
    (cond-it 
     [#t it] 
     [#t 'nope])))

(cond-it ((memq <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-9.html#%_idx_428> 'b '(a b c))                                                  
            (/let/ <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_idx_124> ((it0 it))
              (if-it (memq <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-9.html#%_idx_428> 'y '(x y z)) 
                     (values <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-9.html#%_idx_572> it0 it) 
                     'nope0)))               
           (#t 'nope1))

(require if-it)

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

(/let/ <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_idx_124> ((it 42))
  (cond-it 
   [#t it] 
   [#t 'nope]))

(/let/ <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_idx_124> ((if-it 43))
  (/let/ <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_idx_124> ((it 42))
    (cond-it 
     [#t it] 
     [#t 'nope])))

(cond-it ((memq <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-9.html#%_idx_428> 'b '(a b c))                                                  
            (/let/ <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_idx_124> ((it0 it))
              (if-it (memq <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-9.html#%_idx_428> 'y '(x y z)) (values <http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-9.html#%_idx_572> it0 it) 'nope0)))               
           (#t 'nope1))


;;; Output

#t
#t
#t
. reference to undefined identifier: if-it

-- 
Jens Axel Søgaard





Posted on the users mailing list.