[plt-scheme] Identity of literals in macros
From: Lauri Alanko (la at iki.fi)
Date: Fri Mar 3 12:37:09 EST 2006 |
|
The mzscheme manual says (12.1):
> To mesh gracefully with modules, literal identifiers are compared with
> module-identifier=?, which is equivalent to the comparison behavior of
> R5RS in the absence of modules;
However, I'm beginning to have my doubts about the gracefulness of this.
Granted, it is sortakinda neat that even literal identifiers are handled
by the module system, so they can be e.g. renamed. However, this also
causes problems.
For instance, a well-known trick is to bind unquote to some often-used
function or syntax. This is especially convenient when it's something
that may need to be added or removed often, since one doesn't need to
edit _both_ ends of an expression. For instance, we can use it to add
tracing to a function easily:
(define current-indentation (make-parameter 0))
(define ((trace name f) . args)
(define i (current-indentation))
(define ind (make-string i #\space))
(printf "~a~s ~s -> ...\n" ind name args)
(let ((x (parameterize ((current-indentation (+ i 1)))
(apply f args))))
(printf "~a~s ~s -> ~s\n" ind name args x)
x))
(define-syntax unquote
(syntax-rules ()
((_ f) (trace 'f f))))
So if we want to see e.g. how a fib function works, we just add some
commas:
(define (fib n)
(if (< n 2)
n
(+ (,fib (- n 1))
(,fib (- n 2)))))
However, now that we bound unquote, we can no longer use it in
quasiquote:
> `(one plus two equals ,(+ 1 2))
(one plus two equals (unquote (+ 1 2)))
This is because quasiquote expects to get the particular identifier that
is defined in #%kernel (and bound to an error-raising syntax), and we
just shadowed it.
(Yes, I know I can get `,'-like prefix functionality with custom
readtables. This is just an example.)
Another problem is this:
> (require (lib "contract.ss") (lib "list.ss" "srfi" "1"))
stdin::0: require: duplicate import identifier at: any
in: (require (lib "contract.ss") (lib "list.ss" "srfi" "1"))
Now, contract.ss doesn't really provide a function or macro with the
name "any", but "any" is a special literal identifier used in contract
expressions, and the identifier is bound (again to an error-raising
syntax) in the module so that we need to use the "any" in contract.ss,
not just any old "any".
Even if contract.ss didn't define "any" by itself, then the (usually
unbound) top-level identifier named "any" would be used as the literal,
and improting srfi-1 would shadow that. In any case, we have to rename
one "any" or the other if we want to use contracts and srfi-1 in the
same scope.
Now, this is pretty much the same sort of a problem as the one I
mentioned recently when wishing for multiple namespaces: even if we have
two disjoint syntactic contexts, we cannot use the same identifier in
both places for different purposes. But this is an even simpler case: we
don't need a context-specific namespace here, we just want to be able to
use special keywords in macros without having to care about the bindings
in the current scope.
To my mind, the solution seems to be obvious: forget about identifier
scopes completely and just look at the raw syntax of the expression.
That is, don't compare with module-identifier=?, but simply with
(lambda (i1 i2) (eq? (syntax-object->datum i1) (syntax-object-datum i2)))
Clearly this is considered "ungraceful" in the manual, but I can't
immediately see why. What would this break?
Lauri