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