[plt-scheme] Introducing an Identifier into the Lexical Context of a Macro Cal l
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