Inspired by a comment thread on a related question regarding functions instead of macros.
Is there any way to extend a Scheme syntax definition so that it can use the previous definition of the syntax in the new definition? Furthermore, this must be extensible, that is, it must be possible to chain the technique together several times.
For example, say we want to extend lambda
so that every time a function defined with lambda
is called, it prints "foo" before executing the function body. We can do this in the following way:
(define-syntax old-lambda lambda)
(define-syntax lambda
(syntax-rules ()
((_ args body ...)
(old-lambda args (display "foo") body ...))))
We can also extend this in another way (say, by printing "bar") by doing the following:
(define-syntax old-lambda-2 lambda)
(define-syntax lambda
(syntax-rules ()
((_ args body ...)
(old-lambda-2 args (display "bar") body ...))))
The end result being that functions defined with our new lambda
will print "foo", then "bar" each time they are called.
However, besides polluting the namespace with lots of old-lambda-<x>
, this necessitates making a new old-lambda-<x>
at the source code level each time we do this; this cannot be automated since you can't, say, use gensym
in the syntax definition either. Therefore, there's no good way to make this extensible; the only plausible solution is naming each one old-lambda-print-foo
or something similar to disambiguate, which is obviously not a foolproof solution. (For an example of how this could fail, say two different parts of the code were to extend lambda
to print "foo"; naturally, they would both name it old-lambda-print-foo
, and voila! lambda
is now an infinite loop.) Therefore, it would be very nice if we were able to do this in a way which ideally:
- Doesn't require us to pollute the namespace with lots of
old-lambda-<x>
- Or, failing that, guarantees that we won't have collisions.
In Racket, you can do this with modules. You could create a module that re-exports the entire Racket language except for Racket's lambda
, and exports your new macro under the name lambda
. I'll show one way to arrange the code.
The foo-lambda
module defines and exports the foo-lambda
form, which creates procedures that print "foo\n" when applied.
(module foo-lambda racket
(define-syntax-rule (foo-lambda formals body ...)
(lambda formals (displayln "foo") body ...))
(provide foo-lambda))
The racket-with-foo-lambda
module re-exports the entire Racket language except it provides foo-lambda
under the name lambda
.
(module racket-with-foo-lambda racket
(require 'foo-lambda)
(provide (except-out (all-from-out racket) lambda)
(rename-out [foo-lambda lambda])))
Now you can write a module in this "new language":
(module some-program 'racket-with-foo-lambda
(define f (lambda (x) x))
(f 2))
(require 'some-program)
Note that this doesn't change the Racket version of lambda
, and other Racket forms still use the Racket lambda
binding. For example, if you rewrote the definition of f
above as (define (f x) x)
, then Racket's define
would expand into a use of Racket's lambda
, and you would not get the "foo" printout.
You can chain extensions: each extension is defined in a module that imports the previous version. For example, your bar-lambda
module would import the foo-lambda
module, and so on.
Racket does this internally, in fact. The compiler only understands lambda
with positional arguments, but the Racket language has a lambda
that supports both positional and keyword arguments. The implementation of the Racket language has a module that replaces the built-in lambda
and #%app
(implicitly used to handle function application syntax) with versions that handle keyword arguments.
来源:https://stackoverflow.com/questions/23898908/extensible-macro-definitions