Ok,
So, when working with trying to understand how to implement and work
with hygiene, one of the things that I have discovered is that often
the specification and/or documentation of stuff related to hygiene
contain seemingly random and stupid things. Later on, I have often had
to realize the hard way why it was like that.
I wonder if I have come to another situation like that. SRFI-83 (R6RS
library syntax) contains a seemingly random and ugly limitation; It
doesn't have anything like BH's syntax-begin. What you can do instead
is to specify whether you want to import modules for run-time or
compile-time. Importing srfi-1 for use by macros could look like this
in BH:
(syntax-begin (import srfi-1))
Or like this in SRFI-83:
(import (for srfi-1 expand))
The two rows above are roughly equivalent, but as I understand it
there is no SRFI-83 equivalent of
(syntax-begin (syntax-begin (import srfi-1)))
That is, importing things for use in the macro expansion expansion
phase. So far, I have thought that this is a stupid thing, needlessly
limiting what can be done, plus it's not as theoretically elegant.
But I have realized that there might be a point in doing it like
SRFI-83: It is an elegant way of limiting imports to the first and
second level of the syntactic tower.
Even though you cannot (or at least should not) limit how high the
syntactic tower can be, you can limit all levels above two to not
include any other modules, thus limiting them to the standard library.
You would also need to remove the possibility to have state in levels
higher than the first. (For instance (syntax-begin (define state-var
#f))). In practise this would mean to remove syntax-begin and replace
it with a less powerful construct like the one in SRFI-83, let's call
it import-for-syntax.
I hope this limitation would allow BH to only have at most two
instances of each module at any given time. (Assuming that the
standard library doesn't have any state)
Consider this example:
a.scm:
(import b)
(b-mac)
b.scm:
(import c)
(define-syntax b-mac
(sc-macro-transformer
(lambda (form env)
`(c-mac))))
c.scm:
(import-for-syntax d)
(define-syntax c-mac
(sc-macro-transformer
(lambda (form env)
(my-not #t))))
d.scm:
(define (my-not bool)
(if bool #f #t))
When macro expandng (compiling) modules, the module system would
conclude to do it in the order d, c, b, a. Compiling d, it would
compile two functions, say d#my-not and d~#my-not. Compiling c, it
would compile the c-mac macro (only one instance of it). c-mac would
use the d~#my-not function. Similarly for b, it would compile one
instance of b-mac. When compiling a, it would invoke b-mac, which
would expand to (c-mac), leading to an invocation of that macro, which
would call the compile-time version of my-not.
Doing it this way, it would be possible to allow two modes of
compilation, one for releases, where only run-time versions of
definitions are included, and all macro information is stripped, and
one for development, which also contains compile-time definitions,
macros, and other module information like dependencies and exported
variables and macros.
Does this make sense? To me, this seems like a good compromise between
correctness, power and speed. I can't think of any practical case
where this less powerful way of doing things isn't powerful enough,
although sometimes helper modules for containing macro state would be
needed.
/Per