guile-user
[Top][All Lists]
Advanced

[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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]