Extensible Macros

Inspired by the comment on the corresponding function question instead of macros.

Is there a way to extend the definition of the syntax of a schema so that it can use the previous syntax definition in a new definition? In addition, it must be extensible, i.e. It should be possible to knit a technique several times.

For example, suppose we want to extend lambda so that every time a function defined using lambda called, it displays "foo" before executing the body of the function. We can do this as follows:

 (define-syntax old-lambda lambda) (define-syntax lambda (syntax-rules () ((_ args body ...) (old-lambda args (display "foo") body ...)))) 

We can also expand this differently (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 is that functions defined with our new lambda will print "foo", then "bar" on every call.

However, in addition to polluting the namespace with lots of old-lambda-<x> , this requires creating a new old-lambda-<x> at the source level every time we do this; this cannot be automated because you cannot, say, use gensym in your syntax definition. Therefore, there is no good way to make it extensible; the only plausible solution is to label every old-lambda-print-foo or something like disambiguate, which is obviously not a reliable solution. (An example of how this might happen, let's say that two different pieces of code had to extend lambda to print "foo", naturally, they both would call it old-lambda-print-foo , and voila! lambda now an infinite loop.) Therefore, it would be very nice if we could do it in a way that is ideal:

  • Doesn't require us to pollute the namespace with old-lambda-<x>
  • Or, if it is not, it guarantees that we will not have collisions.
+7
macros inheritance scheme r5rs
source share
1 answer

In Racket, you can do this with modules. You can create a module that re-exports the entire Racket language except Racket lambda and exports your new macro called lambda . I will 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 relays the entire Racket language, except that 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 does not change the version of Racket lambda , and other Racket formats still use the Racket lambda binding. For example, if you rewrote the definition of f above as (define (fx) x) , then Racket define would expand to use Racket lambda and you would not get a printout of "foo".

You can connect extensions: each extension is defined in a module that imports a previous version. For example, your bar-lambda module imports the foo-lambda module, etc.

A racket does this internally, essentially. The compiler only understands lambda with positional arguments, but the Racket language has lambda that supports both positional and keywords. The Racket language implementation has a module that replaces the built-in lambda and #%app (implicitly used to process the syntax of functional applications) with versions that process keyword arguments.

+1
source share

All Articles