[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: How to use-modules within macro?
From: |
Mark H Weaver |
Subject: |
Re: How to use-modules within macro? |
Date: |
Wed, 04 Sep 2019 15:54:13 -0400 |
User-agent: |
Gnus/5.13 (Gnus v5.13) Emacs/26.3 (gnu/linux) |
Hi Florian,
"pelzflorian (Florian Pelz)" <address@hidden> writes:
> To retain unhygienic references, I am now using datum->syntax instead
> of local-eval. It is much better. For example, to make a macro that
> increments all numbers in a given program by one:
>
> (use-modules (ice-9 match))
> (define-syntax one-more
> (lambda (x)
> (syntax-case x ()
> ((_ exp)
> (datum->syntax
> #'exp
> (let loop ((y (syntax->datum #'exp)))
> (match y
> ((? number?) (1+ y))
> ((? list?) (map loop y))
> (else y))))))))
>
> (let ((four 4))
> (one-more (* 2 3 four)))
>
> Yields 48. I hope this is the right approach for rewriting programs.
There are some problems above:
(1) The first argument to 'datum->syntax' must be an identifier, which
is the syntax object corresponding to a symbol. Here, you are
passing an entire expression, and in the example usage above, #'exp
will be the syntax object corresponding to (* 2 3 hour). Guile
should ideally raise an error in this case.
(2) The way you are doing things here destroys hygiene within the
expression that you are rewriting. You convert the entire
expression with 'syntax->datum', process the datum, and then convert
the rewritten expression using 'datum->syntax'. The problem here is
that 'syntax->datum' discards all of the extra information about
lexical environments of identifiers that were kept in the syntax
object. This will cause severe problems when 'one-more' is used in
combination with other macros, including unintended variable
capture.
To do this properly, you must do the rewriting on the syntax objects
themselves. It's okay to convert a syntax object to a datum to test
whether it's a literal number, but the important thing is that all
*identifiers* in the rewritten code should be preserved.
So, instead of using 'match' on the result of 'syntax->datum', you
should instead use 'syntax-case' on the syntax object itself, like this
(untested):
(let loop ((e #'exp))
(syntax-case e ()
(num
(number? (syntax->datum #'num))
#'(1+ num))
((x ...)
(map loop #'(x ...)))
(y
#'y)))
Finally, I should mention that macro expansion is always done from the
outside in, meaning that when 'one-more' is expanded, its operand will
not yet have been expanded. In general, this means that it's impossible
to comprehend the code within a macro's operands unless the parsing code
knows about every macro that might be used within the operands. It's
not even possible to know which subparts are expressions and which are
other things like variable binding lists.
For this reason, I think it's generally a mistake to try to parse code
within a macro's operands. It normally only makes sense for macros to
inspect the parts of operands that are considered part of the macro's
syntax. For example, it makes sense for a 'let' macro to parse its
binding list, or for a 'match' macro to parse its patterns and
templates, but it does *not* make sense for a macro to try to parse
general subexpressions passed to the macro.
If you could give me a birds-eye view of what you're trying to do here,
I might be able to suggest other approaches.
Best,
Mark