One of the curious facts about syntax-rules is that it
specifies transformations of source with a kind of
semi-agnosticism about whether the output of a
transformation is an expression or a syntactic datum.
To whit, please consider the following examples. We'll
start with some definitions:
(define counter 0)
(define (count) (set! counter (+ 1 counter)) (list counter))
(define-syntax synhack
(syntax-rules ()
((_ arg) (arg (count)))))
This is reasonably clear enough. After those definitions:
(synhack list) ==> ((1))
Note that when SYNHACK expands "(count)" there is some
ambiguity, though. It might not mean an expression but
rather a syntactic datum:
(synhack quote) ===> (count)
Fair enough, although it puzzles me a bit that the syntactic
datum interpretation doesn't run very deep:
(quote (synhack list)) ===> (synhack list) ;; not (list (count))
I understand the practicality of this arrangement in an ad hoc
way but there does seem to be an odd asymmetry there: I am
a bit unclear as to the desirability of defining an expansion without
being able to tell in advance whether the expansion is to an
expression
or to a syntactic datum.
On the one hand, SYNHACK is not merely answering the question
of "what expression does this expand to" but is also answering
the question "what is the source code of the expression that this
would expand to".
On the other hand, there is no way apparent to write a macro
whose expansions are specifically intended to represent forms
or subforms which are syntactic datums.
So my questions are:
1) Is this a good state of affairs?
2) Were these questions considered in the history of the
design of syntax-rules and their ratification as standard?
Please understand that I'm *not* trying to "undo" the ratification
of syntax-rules as standard. That's water under the bridge and
they are quite workable in spite of these quirks. I'm just curious
about this aspect of them - of opinions concerning their nature and
of accounts of the history of this aspect.
I have a *vague* notion that there could be a "better" system
that eliminates the ambiguity with separate facilities for
expression-syntax-rules and datum-syntax-rules but no clear
notion of what such a system ought to look like in much detail.
Thanks,
-t
I'd split your point in two cases:
1. With the following syntax rule:
((_ arg) (arg exp))
you don't know in advance, what will be done to "exp" (whether it will
it be expanded or quoted or whatever else). It depends on what "arg"
is. And I think it is just what one wants when he/she writes a macro
expanding to a form with arbitrary head ("arg" in this case).
2. Quote and syntax-rules are static in a sense that you cannot quote
more than you see, (i.e. "(quote expr)" cannot evaluate to the
quotation of what expr is expanded to) or operate on what your
arguments expand to in a syntax-rule (at least in implementation-
specific way). I think it's ok as long as we don't want to extend
syntax-rules with a way to control expansion and a set of core forms
to which everything expands.
> (define counter 0)
> (define (count) (set! counter (+ 1 counter)) (list counter))
> (define-syntax synhack
> (syntax-rules ()
> ((_ arg) (arg (count)))))
>
> This is reasonably clear enough. After those definitions:
>
> (synhack list) ==> ((1))
>
> Note that when SYNHACK expands "(count)" there is some
> ambiguity, though. It might not mean an expression but
> rather a syntactic datum:
>
> (synhack quote) ===> (count)
>
> Fair enough, although it puzzles me a bit that the syntactic
> datum interpretation doesn't run very deep:
>
> (quote (synhack list)) ===> (synhack list) ;; not (list (count))
>
> I understand the practicality of this arrangement in an ad hoc
> way but there does seem to be an odd asymmetry there: I am
> a bit unclear as to the desirability of defining an expansion without
> being able to tell in advance whether the expansion is to an
> expression
> or to a syntactic datum.
I'm not sure I understand the asymmetry here. To be clear in understanding
this, let me try to approach the situation a little differently. Let's
first say that given an expression (and here I'll include definitions and
any other valid Scheme form), we want to understand the result of
evaluating that form. Usually, when we use the term evaluation, we talk
about post-expansion execution of the code, but for clarity, let me use
evaluation to represent both the process of expanding Scheme code and also
of executing that Scheme code. There are two distinct and important phases
here, conceptually, regardless of whether an evaluator intermingles these
or does them in two passes. For any given form, it must be first expanded,
and then executed, in order to obtain the final result (which is a value).
Why do I make this distinction? Because we must have a way of clearly
understanding what macros do and why they exist. Macros exist so that you
can abstract your code at the syntax level. Procedural abstraction allows
you to receive values and return values. They abstract away execution.
Macros receive syntax and return syntax, and allow you to abstract away
syntax transformations. More specifically, syntax-rules is a special case
of general syntactic abstraction. Generally speaking, you could think of
macros as procedures that operate with syntax objects. Syntax rules is a
language for writing these procedures. The sorts of procedures that you
can write is more limited than the sort of transformations on syntax
objects that you might be able to do with syntax-case, but it is a fairly
sophisticated language none the less. It is a term rewriting language.
Now, if we consider macros as procedures, we run into a problem. The point
of macros is to abstract syntax, which has to be done before execution of
regular procedures can take place. Macros in Scheme eventually must either
loop endlessly or terminate with a syntax object composed of only core
forms. These core forms or procedure calls have no macros in them. They
represent purely procedural abstraction.
So, this results in two phases. You can think of both phases as the
application of various procedures to objects, but in the expansion phase,
you are applying any macro transformers in the code to the syntax objects
with which they have been called, and doing so in a recursive manner. That
is, the expander essentially calls all the macros until there are no more
macros to be called, at which point the code is in its expanded state, so
that it can be executed.
So let's think about your example above. The syntax transformer (macro)
synhack expects to receive a single syntax object and uses it as the first
element in the returned syntax object. This returned syntax object will
then be expanded again by the expander, and so forth, until no more macros
are found throughout. In your two cases, you use the quote and list syntax
objects as the element to pass to synhack. The results are the same,
syntax object (list (count)) and (quote (count)). Now, there are no more
macros (though quote is a special core form, which is sort of a macro, but
not really, in the sense that I mean it here) in either one of these.
QUOTE is a core form that tells the executor to halt execution for its
argument. List is a procedure that takes values and returns a list of
those values. One is a procedure, which may or may not be a core form,
whereas the other, quote, I am pretty sure is a core form on most Scheme
systems, and I don't know any normal Scheme implementation that doesn't
have this as a core form.
So, there really isn't any asymmetry here. synhack just rewrites or alters
syntax. It has nothing to do with execution. It just manipulates the
syntax into some other form. It doesn't care underneath what that form
will actually do when it is executed; that is not its job. That is to say,
when writing macros, we are not concerned with writing expressions or
data, we're concerned with rewriting syntax into other syntax. Whether
these are interpreted as expressions is sort of a red herring.
Actually, you also may want to really think of both (list (count)) and
(quote (count)) as expressions. They are both things that will be
executed, and have no further expansion. When executed, they both return
or result in values. One returns a list of the values of evaluating its
arguments, while the other returns the value of treating its arguments as
data. The difference between them is that one is a core form, while the
other is just a procedure.
In conclusion, it is very desirable to have a general means of
transforming syntax objects in a programmatic way that is accessible and
exposed to the programmer in a well defined way. It does not make sense to
make ad hoc restrictions to the sorts of syntax that we can produce from a
transformer (though, as in the case of syntax-rules, we may have a reason
to control the expressivity of the programming language for writing these
transformers). In other words, this is the symmetric way of handling
things, and to make special cases for particular core forms as opposed to
other non-core forms in macros would be more asymmetrical than allowing it
all.
Aaron W. Hsu
--
A professor is one who talks in someone else's sleep.