Thanks
--tim
Various people have attempted to write something like this, and while
there have been various attempts, nothing I have seen adequately prepares
someone for writing hygienic and hygiene-bending macros in syntax-case (or
really, any other system for that matter). Doing so is different than
writing CL macros, and I would argue, generally more pleasant.
I would recommend the main papers on syntax-case and its workings as a
good supplement to the guides, and you can also check out TSPL4 [1] as an
additional reference to the syntax-case system, variations (minor, ish) of
which are in all R6RS capable Schemes (Racket included).
Aaron W. Hsu
[1] http://www.scheme.com/tspl4/
--
Programming is just another word for the lost art of thinking.
Both Guile Scheme and Bigloo Scheme support define-macro;
I presume that other Schemes do also.
(define-macro (inc! x . delta)
(if (null? delta)
`(set! ,x (1+ ,x))
`(set! ,x (+ ,x ,@delta))))
> Both Guile Scheme and Bigloo Scheme support define-macro;
> I presume that other Schemes do also.
Indeed, any Scheme that supports syntax-case can have a form of defmacro.
However, one should not program using defmacro in Scheme. Doing so is
about as close to intentionally introducing bugs into your programs as I
can think of at the moment.
Aaron W. Hsu
I found R5RS pretty easy to follow. See section 7.3 for some examples.
Also see section 12 in R6RS library.
> Hi, does anyone know of a guide to Scheme macros
> (specifically to Racket's: I am not sure how standardised
> macros are now, though I know they used not to be) for
> someone very familiar with CL macros?
Racket's macro facilities are the most powerful and also the
most complex among Scheme implementations, as far as I can
tell. I suggest you to take this approach:
1. Learn syntax-rules.
2. Learn the syntax-case library.
3. Learn Rackets macro system.
For syntax-rules: I suggest you to search for "syntax-rule
tutorial" and pick any of the short introductions you find
this way, just to get you started; then read "JRM's
Syntax-rules Primer for the Merely Eccentric"[1], which is
long and should satisfy your needs.
For syntax-case: I have found neither satisfying
documentation, nor tutorials, for learning it without pain;
this includes my own introduction to the R6RS relevant
chapter[2]. I think that both the chapter in the R6RS
document and the chapter in "The Scheme Programming
Language"[3] are for people who "already get it", the same
goes for all the papers I have seen so far: people writing
those have long forgotten what does it mean to be a
beginner. Also, to learn syntax-case one has to build a
mental model of how the macro expander works, and it is
totally opaque at first. The only way is to have patience
with yourself and try to read everything you stumble upon.
I have not learned Racket macros, so I cannot suggest
anything; when I tried to read their documentation without
knowing syntax-case first, I understood almost nothing; now
that I know a bit of syntax-case I can understand something
more of what I remember; so I guess that, after learning
syntax-case, you will be able to use their reference
material with some success.
HTH
[1] <http://eval.apply.googlepages.com/eccentric.txt>
[2] <http://marcomaggi.github.com/docs/nausicaa.html/stdlib-syntax_002dcase-intro.html>
[3] <http://www.scheme.com/tspl4/syntax.html#./syntax:h0>
--
Marco Maggi
> Various people have attempted to write something like this, and while
> there have been various attempts, nothing I have seen adequately
> prepares someone for writing hygienic and hygiene-bending macros in
> syntax-case (or really, any other system for that matter). Doing so is
> different than writing CL macros, and I would argue, generally more
> pleasant.
Thanks, and thanks to the other responses.
One thing I probably should have made more clear is that I'm at the
point where I can pretty easily write things with syntax-rules but
there are things I want to do which I think it is preventing me from
doing (as it should) so I think I need to understand syntax-case &
friends more thoroughly.
The "things I want to do" are essentially introduce bindings which the
user of the macro can see (which I think is the same thing as "not be
hygenic"), so I want something like this to "work":
(define-syntax bodging
(syntax-rules ()
((bodging form ...)
(let ((x 1)) form ...))))
(bodging x)
I do understand why syntax-rules is making it not "work", and I'm glad
it does so in general.
I'm aware that there are things out there which essentially look like
the CL macro system, but I don't want to use them as I already
understand that (though, if they can be implemented in terms of
syntax-case &c it might well be interesting to look at their
implementation to see how they do it).
--tim
> I'm aware that there are things out there which essentially look like
> the CL macro system, but I don't want to use them as I already
> understand that ...
I don't think that's a valid reason not to use them, on the contrary.
In addition to already understanding them, they would have the advantage
of letting you write code more easily transposable to Common Lisp.
--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.
> I don't think that's a valid reason not to use them, on the contrary.
Since my purpose is to learn the Scheme (specifically Racket) macro
system, I rather think it is.
> (define-syntax bodging
> (syntax-rules ()
> ((bodging form ...)
> (let ((x 1)) form ...))))
> (bodging x)
> I do understand why syntax-rules is making it not "work",
> and I'm glad it does so in general.
Good. So you can understand why the following works:
(define-syntax bodging
(syntax-rules ()
((bodging var form ...)
(let ((var 1)) form ...))))
(bodging x x)
by expanding to:
(let ((x 1)) x)
;; ^ ^
;; | |
;; | -- reference position
;; -- binding position
To be able to write:
(bodging x)
and achieve the same result, we want to generate the X
identifier in the binding position as if it was present in
the input form; here:
(define-syntax bodging
(lambda (macro-use-syntax-object-holding-input-form)
(syntax-case macro-use-syntax-object-holding-input-form ()
((bodging form ...)
(let ((the-dirty-identifier (datum->syntax (syntax bodging) 'x)))
(quasisyntax
(let (((unsyntax the-dirty-identifier) 1))
form ...)))))))
or more briefly but completely equivalent:
(define-syntax bodging
(lambda (stx)
(syntax-case stx ()
((bodging form ...)
#`(let ((#,(datum->syntax #'bodging 'x) 1))
form ...)))))
notice how QUASISYNTAX, UNSYNTAX and SYNTAX are similar to
QUASIQUOTE, UNQUOTE and QUOTE and the abbreviations:
#` #, #'
are similar to the abbreviations:
` , '
HTH
--
Marco Maggi
May be this will help? http://blog.scheme.dk/2006/05/how-to-write-unhygienic-macro.html
> Good. So you can understand why the following works:
>
> (define-syntax bodging
> (syntax-rules ()
> ((bodging var form ...)
> (let ((var 1)) form ...))))
>
> (bodging x x)
Yes, that was as far as I had got (and actually, that's almost
certainly stylistically preferable to what I'm aiming at, but as I said
what I'm trying to do is understand the guts of the macro system, not
write pretty macros...).
I will study the rest of your article: thank you!
> Racket's macro facilities are the most powerful and also the
> most complex among Scheme implementations, as far as I can
> tell.
Among Schemes, I believe that Chez Scheme and Racket hold the ``honors''
of most powerful and subsequently most involved macro systems. Power,
though, makes for interesting debates, so I am hesitant to make absolute
claims about power when it comes to syntax-case. Racket is less forgiving
in certain areas related to macro systems (though not its macro system
proper) than Chez is, including libraries and phasing. However, both have
powerful extensions to the R6RS syntax-case that go beyond what I believe
any other syntax-case based implementation does.
> Tim Bradshaw wrote:
There are some stylistic things one might want to try if you want to see
different ways of doing this:
(define-syntax (bodging x)
(syntax-case x ()
[(k form ...)
(with-implicit (k x)
#'(let ([x 1]) form ...))]))
(define-syntax (bodging x)
(syntax-case x ()
[(k form ...)
(with-syntax ([x (datum->syntax #'k 'x)])
#'(let ([x 1]) form ...))]))
I am not sure how many of these forms and extensions Racket has, but they
are trivial to provide and writing those might be an interesting exercise.
The with-implicit form [1] makes it cleaner to do these sorts of things,
and the with-syntax form allows a cleaner final syntax, so that you do not
need to use quasisyntax and quasiunquote.
Just to give you some variety to practice your macros on.
If you really want to play around with how hygiene works, there is an
interesting example in CSUG8 that shows how one might implement a module
system with interfaces and implementation separately by writing macros
around a combined module form `module'. [2] It's a good exercise, and you
can write a naive `module' form for Racket as well to try these things
out. There are some gotchas to watch out for as well. [3]
Aaron W. Hsu
[1] http://www.scheme.com/csug8/syntax.html#./syntax:s11
[2] http://www.scheme.com/csug8/syntax.html#./syntax:s19
[3] http://my.opera.com/arcfide/blog/2010/08/25/syntax-brain-twister
> [...] and the with-syntax form allows a cleaner final syntax, so
> that you do not need to use quasisyntax and quasiunquote.
At least in the Racket case, `quasisyntax' is implemented using
`with-syntax'. (If fact, it had `with-syntax' when plt switched to
`syntax-case' macros, and I made a feature request for something like
quasiquotes that make some uses easier.)
--
((lambda (x) (x x)) (lambda (x) (x x))) Eli Barzilay:
http://barzilay.org/ Maze is Life!
> 2. Learn the syntax-case library.
(`syntax-case' is not a library, it's just a way to deal with syntax
values via pattern matching. (And I hope that you don't use "library"
as in "learn the implementation" -- that would be an overkill.))
> 3. Learn Rackets macro system.
Knowing about `syntax-case' should be enough for almost all practical
cases cases.
> For syntax-rules: I suggest you to search for "syntax-rule
> tutorial" and pick any of the short introductions you find
> this way, just to get you started; then read "JRM's
> Syntax-rules Primer for the Merely Eccentric"[1], which is
> long and should satisfy your needs.
That tutorial goes from relatively simple uses to complicated ones
that simulate computations using rewrite rules. It's cute to know
that these things are available in a system as simple as
`syntax-rules', but when you have a "low level" macro system where you
define plain functions (instead of being limited to rewrite rules)
there's no need for that kind of obscure gymnastics.
> Also, to learn syntax-case one has to build a mental model of how
> the macro expander works, and it is totally opaque at first.
I very strongly disagree with this. I consider myself sufficiently
fluent with `syntax-case' macros, and I know only a bit about the
implementation (including "how the macro expander works") -- and I'm
intentionally avoiding reading such details to keep my understanding
at the "user level".
[Because of this the following explanation is rough, but it should be
fine to understand how to use `syntax-case'. No need to flame with
the 2000 mistakes I'm liable to make.]
The main idea with Racket's macro system (and probably with other
`syntax-case' systems) is that macros are syntax to syntax functions,
just like `defmacro', except that instead of raw s-expressions you're
dealing with syntax object. This becomes very noticeable when
identifiers are handled: instead of dealing with symbols, you're
dealing with these syntax values (called "identifiers" in this case)
that are essentially a symbol and some opaque information that
represents the lexical scope for its source. In several `syntax-case'
systems this is the only difference from `defmacro' macros, but in the
Racket case this applies to everything -- identifiers, numbers, other
immediate constants, function applications, etc -- they are all the
usual sexpr values that you're used to, except wrapped with additional
information. Another thing that is unique to Racket is this extra
information: in addition to the opaque lexical context, there is also
source information and arbitrary properties (there are also
certificates, but that's ignorable for this level.).
With this in mind, explaining the rest is not too difficult:
* (syntax-source stx), (syntax-position stx), (syntax-line stx),
(syntax-column stx) -- retrieve parts of the source information.
* (syntax-e stx) -- takes a syntax value and returns the value it
"wraps". For example, if `stx' is an identifier you'd get a symbol,
and if it's a number you'd get the number etc. If it's a
parenthesized form, you'd get a list of syntax values for the
subforms. Note that the list can be improper, with the last
element being a syntax object that contains a proper list.
* (syntax->list stx) -- since `syntax-e' might return an improper list
for a list syntax, this utility makes things easier and will use
`syntax-e' again in these cases and will return a proper list. If
the syntax is itself not a proper list, it returns #f.
* (syntax->datum stx) -- takes a syntax value and returns the plain
s-expression that it holds. This is done by recursive uses of
`syntax-e'. (It would be a simple definition.)
* (syntax-property stx prop) -- returns the given property value from
stx, if any, and #f if none. For example, try
(syntax-property #'[foo] 'paren-shape)
* There is no accessor for the lexical scope (and you don't need one).
* To create a piece of syntax you use `datum->syntax', and you give it
an s-expression which will be the "contents" of the resulting syntax
object. (The input can contain syntax values, which are left as
is.) But when you do that you need to give it the other bits --
including the lexical context thing, which you have no access to.
The way that's done is:
(datum->syntax context-stx input-sexpr)
This returns a syntax value that wraps the `input-sexpr' value,
using the lexical scope from `context-stx'. There is actually
another optional argument that specifies the source (either using
another syntax object, or as an explicit list), and another for
copying the properties from, etc. So a common thing to do when you
want to create an unhygienic user-visible binding is:
(datum->syntax something 'foo something something)
where `something' is some syntax value that you get from the user
input to the macro. It makes a `foo' identifier that has the same
lexical context information, same source, and same properties.
* There is also (quote-syntax blah) which creates a quoted syntax,
with its lexical source from the place it appears.
* Finally, `define-syntax' does the magic of tying a name with a
transformer function.
And that's almost everything that you need in order to write hygienic
(and non-hygienic) macros. Very inconveniently.
For example, here's a simple `while' macro:
(define-syntax (while stx)
(define subs (syntax->list stx))
(datum->syntax
stx
`(let loop ()
(when ,(cadr subs)
,@(cddr subs)
(loop)))
stx))
which breaks like this:
(define x 2)
(let ([let 5])
(while (< x 10) (printf "x = ~s\n" x) (set! x (add1 x))))
The problem is that all of those quoted names are getting the context
of the user input, which is not the right thing (it's close to a
defmacro). To fix this, you need to `quote-syntax' all of these
identifiers, so they'll have the macro source instead of the input
source (to try this, you'll need (require (for-syntax racket/base)),
but that's a different story):
(define-syntax (while stx)
(define subs (syntax->list stx))
(datum->syntax
stx
`(,(quote-syntax let) ,(quote-syntax loop) ()
(,(quote-syntax when) ,(cadr subs)
,@(cddr subs)
(,(quote-syntax loop))))
stx))
But that's clearly insane... More than being tedious, it's still
incorrect since all of those parens will have the user's lexical
context (which means that it will use the user's notion of function
application via the special `#%app' macro). Instead of doing this, a
better approach would be to create the resulting syntax with the
lexical context of the macro source by changing just that argument:
(define-syntax (while stx)
(define subs (syntax->list stx))
(datum->syntax
(quote-syntax here)
`(let loop ()
(when ,(cadr subs)
,@(cddr subs)
(loop)))
stx))
And that's simple again, and works fine.
The problem is that it's tedious wrt to deconstructing the input
(which is trivial in this case), and wrt slapping together an output
value -- and that's where `syntax-case' comes in. It addresses the
both by using pattern matching, where identifiers in patterns are
bound as "syntax patterns". A new form is added -- `syntax' -- which
is similar to a `quote', except that (a) it actually quotes things
similarly to `quote-syntax', with the lexical context of the `syntax'
form; and (b) pattern variables are substituted with what they
matched. The above macro becomes much easier:
(define-syntax (while stx)
(syntax-case stx ()
[(_ test body ...)
(syntax (let loop () (when test body ... (loop))))]))
The first line specifies that you want to match the `stx' input
syntax, and that you have no "keywords" (in the same sense as in
`syntax-rules'). The second line is the pattern that is matched
against this input -- with two pattern variables that match the second
subexpression and the sequence of expressions from the third and on.
(The first subexpression is matched against `_' which matches anything
and doesn't bind a pattern variable -- it's often not needed, since
it's just the macro name.) The last line is the output, specified
using `syntax', which means that it's very similar to the previous
version where everything is given the lexical context of the macro and
the two pattern variables are replaced with the two matches (which
means a splicing subtitution for `body').
Now, say that you want an unhygienic user-visible piece of syntax.
For example, bind the always entertaining `it' thing to the test
result. This:
(define-syntax (while stx)
(syntax-case stx ()
[(_ test body ...)
(syntax (let loop ()
(let ([it test])
(when it body ... (loop)))))]))
won't work because `it' has the macro source -- it's hygienic and
therefore not visible. Instead, you need to use `datum->syntax' with
the user syntax:
(define-syntax (while stx)
(syntax-case stx ()
[(_ test body ...)
(let ([it (datum->syntax stx 'it)])
(syntax (let loop ()
(let ([it test])
(when it body ... (loop))))))]))
But this doesn't really work since `it' needs to be bound as a pattern
variable rather than a plain binding. `syntax-case' can be used here
again: (syntax-case <something> () [foo <body>]) will match `foo'
against the <something> syntax, and it will be bound as a pattern
variable in the <body>. So:
(define-syntax (while stx)
(syntax-case stx ()
[(_ test body ...)
(syntax-case (datum->syntax stx 'it) ()
[user-it
(syntax (let loop ()
(let ([user-it test])
(when user-it body ... (loop)))))])]))
Finally, there are some more conveniences. First, `with-syntax' is a
macro that binds pattern variables (by a similar translation to
`syntax-case'):
(define-syntax (while stx)
(syntax-case stx ()
[(_ test body ...)
(with-syntax ([user-it (datum->syntax stx 'it)])
(syntax (let loop ()
(let ([user-it test])
(when user-it body ... (loop))))))]))
and there's the #' reader macro for `syntax':
(define-syntax (while stx)
(syntax-case stx ()
[(_ test body ...)
(with-syntax ([user-it (datum->syntax stx 'it)])
#'(let loop ()
(let ([user-it test])
(when user-it body ... (loop)))))]))
and there are also #` and #, and #,@ which are implemented by
translating them to uses of `with-syntax'.
As a side note, it's possible now to see that there are several
options for the lexical context of the `it' identifier -- it can use
the input syntax as above, or the expression #'test, or the head form
(which would be #'_ if _ wasn't ignored as a pattern variable).
There is one more important aspect to the Racket macro system: it
separates the "runtime phase" from the "syntax phase". This is why I
said above that (require (for-syntax racket/base)) is needed. Roughly
speaking, this makes sure that source code is compilable by having
each level live in its own world, so macros are limited to dealing
with input syntax only. (For example, a CLOS implementation in this
system cannot check the value of a class to determine how some macro
should expand.)
> [...] Racket is less forgiving in certain areas related to macro
> systems (though not its macro system proper) than Chez is, including
> libraries and phasing.
This would be similar to saying that Scheme is less forgiving than
JavaScript is when it comes to adding strings -- it throws an error
for (+ 1 "2"), instead of returning "12". IMO (note the "M") think
that "less forgiving" in both cases is desirable in avoiding confusing
bugs.
> [...]
Did you look at the guide, BTW?
http://docs.racket-lang.org/guide/macros.html
> The "things I want to do" are essentially introduce bindings which
> the user of the macro can see (which I think is the same thing as
> "not be hygenic"), so I want something like this to "work":
>
> (define-syntax bodging
> (syntax-rules ()
> ((bodging form ...)
> (let ((x 1)) form ...))))
Hopefully the explanation I gave in the other post should clarify it.
But note that doing so is usually considered a bad thing, and can get
confusing with macros that have inputs that come from other macros. A
more robust way to do this kind of thing in racket is to keep things
hygienic -- you define a new `it' (or `x' in the above) keyword that
is usually a syntax error, except when it's used inside your macro. I
described this a while ago here:
http://blog.racket-lang.org/2008/02/dirty-looking-hygiene.html
> I'm aware that there are things out there which essentially look
> like the CL macro system, but I don't want to use them as I already
> understand that (though, if they can be implemented in terms of
> syntax-case &c it might well be interesting to look at their
> implementation to see how they do it).
In the Racket case, the "defmacro" library does that -- you can see
its high level in "collects/mzlib/defmacro.rkt", and the actual work
is done in "collects/mzlib/private/dmhelp.rkt". What it does is
convert the input syntax to a plain sexpr but keep a hash table that
maps the sexpr (and is subparts) to the corresponding syntax values.
Then, the macro function is applied on this sexpr. Finally, pieces of
the resulting sexpr that are found in this hash table are replaced
with the original syntax that they came from, and the whole thing is
then turned back into syntax using `datum->syntax' with the input
syntax for lexical context.
Yes, in fact that's how I learnt what I now know about syntax-rules and
so on. I then got somewhat confused by the syntax-case stuff and was
too lazy to fight my way through it, though I think (thanks mostly to
responses here) I'm now in a good position to unconfuse myself.
Most of the problem, really, is that I know the CL macro system - which
is the FORTRAN IV of macro systems - so well that it's impeding my
understanding of how Scheme's work I think.
>
> Hopefully the explanation I gave in the other post should clarify it.
> But note that doing so is usually considered a bad thing, and can get
> confusing with macros that have inputs that come from other macros. A
> more robust way to do this kind of thing in racket is to keep things
> hygienic -- you define a new `it' (or `x' in the above) keyword that
> is usually a syntax error, except when it's used inside your macro. I
> described this a while ago here:
That's actually quite close to my target case, which is to be able to
emulate (for self-pedagogical purposes) a macro I have in CL which lets
you say:
(collecting
...
(collect x)
...
(foo (lambda (x) (collect x)))
...)
where COLLECT is a local macro (in the current CL implementation: it
should actually be a local function but I didn't understand that when I
wrote a very long time ago, hence the annoying wrapping-it-in-lambda
thing above) which will collect its argument into a list, with the
result of the whole form being the list.
In CL, COLLECT obviously needs to exist as a symbol even outside of the
macro: in the current implementation it does not have a global
definition, but it actually should have one which simply signals an
error. So that's pretty like your example.
Once again thanks to everyone who's answered here: I didn't expect to
get such a lot of good answers.
--tim
PS please no-one write collecting/collect for me: the whole point is
that I want to understand how to do it, not that I actually need the
macro.
> or more briefly but completely equivalent:
>
> (define-syntax bodging
> (lambda (stx)
> (syntax-case stx ()
> ((bodging form ...)
> #`(let ((#,(datum->syntax #'bodging 'x) 1))
> form ...)))))
And the answer for my test case dirty macro is this:
(define (call/collecting thunk)
;; mindless reverse-the-list version
(let ((s '()))
(thunk (lambda (e)
(set! s (cons e s))))
(reverse s)))
(define-syntax (collecting stx)
(syntax-case stx ()
((collecting)
#'(quote ()))
((collecting form ...)
#`(call/collecting (lambda (#,(datum->syntax stx 'collect))
form ...)))))
And now, for instance, (collecting (collect 1) (collect 2)) -> (1 2).
Which is delightfully easy. There's obviously a lot to understand
here, but I'm making progress.
Is the opposite true? Can syntax-case be implemented on top of
defmacro?
>> Also, to learn syntax-case one has to build a mental
>> model of how the macro expander works, and it is totally
>> opaque at first.
>
> I very strongly disagree with this.
> [...]
> And that's almost everything that you need in order to
> write hygienic (and non-hygienic) macros. Very
> inconveniently.
Uff... Probably unconsciously, you make it sound like "if
you do not get it, you do not deserve to code in Scheme",
which is a bit annoying. ;-)
People have difficulties in learning Scheme's hygienic
macros, it is a fact for me. Even if, at some point in your
life, you are comfortable in using an "ordinary"
preprocessor (like GNU m4), IMHO there is very little you
can reuse in your brain from past experiences in
non-esoteric languages: Scheme hygienic macros are on
another level.
My 2 cents.
--
Marco Maggi
Thanks for this post Eli, it was the most useful explanation of macros
that I have yet read.
Cheers,
David
This can be answered with a strong and resounding "maybe". ;)
See http://dx.doi.org/10.3217/jucs-016-02-0271
Pascal
--
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
> People have difficulties in learning Scheme's hygienic macros,
> it is a fact for me. Even if, at some point in your life, you are
> comfortable in using an "ordinary" preprocessor (like GNU m4),
> IMHO there is very little you can reuse in your brain from
> past experiences in non-esoteric languages: Scheme hygienic
> macros are on another level.
Not another level: another language.
That's what's so great with Common Lisp macros:
they're written in Common Lisp.
(rnrs syntax-case) is one of the standard R6RS libraries.
I interpreted Marco Maggi's list of suggestions as:
1. First, learn syntax-rules, which is highly portable
(available in all implementations of the R5RS or R6RS)
and easy to use.
2. Second, learn how to use the (rnrs syntax-case)
library, which is moderately portable (available in all
implementations of the R6RS) and not as complex as the
full macro system provided by Racket.
3. Finally, learn the remaining features of Racket's
macro system.
Will
Note that in many cases, it's possible to do something like the following:
#;> (define-syntax bodging
(syntax-rules ()
((bodging id form ...)
(let ((id 1)) form ...))))
#;> (bodging x x)
1
This is something that more often than not, you also want to do in
Common Lisp, because in general you want to allow for nested uses of
macros, and then it becomes necessary and/or easier to understand to
explicitly refer to what the macros introduce by different names.
> This is something that more often than not, you also want to do in
> Common Lisp, because in general you want to allow for nested uses of
> macros, and then it becomes necessary and/or easier to understand to
> explicitly refer to what the macros introduce by different names.
Yes, and in fact that's what a rationalised version of my original CL
macro should do (and in fact I have a better version which does
something like this, but with multiple collectors: one of my next
things to do is to work out how to do this in Scheme (again, to help
learn the macro language, not because I need it)).
(Note that in my answer, the various choices of the lexical scope to
use for the made up identifier matter in exactly this respect -- which
is exactly why the above is still a valid point. The example of
syntax parameters makes it somewhat easier, but nothing beats user
code specifying the identifier -- the problem withat that, of course,
is that you need tp specify the identifier...)
> Eli Barzilay wrote:
>
>>> Also, to learn syntax-case one has to build a mental
>>> model of how the macro expander works, and it is totally
>>> opaque at first.
>>
>> I very strongly disagree with this.
>> [...]
>> And that's almost everything that you need in order to
>> write hygienic (and non-hygienic) macros. Very
>> inconveniently.
>
> Uff... Probably unconsciously, you make it sound like "if
> you do not get it, you do not deserve to code in Scheme",
> which is a bit annoying. ;-)
Not at all. My point was that a `syntax-case'-based system is not
doing any strange and complicated magic behind your back.
> People have difficulties in learning Scheme's hygienic
> macros, it is a fact for me. Even if, at some point in your
> life, you are comfortable in using an "ordinary"
> preprocessor (like GNU m4), IMHO there is very little you
> can reuse in your brain from past experiences in
> non-esoteric languages: Scheme hygienic macros are on
> another level.
Sure they are on another level. Simple `defmacro'-style macros are
"more complicated" than M4 or CPP or whatever string-based system you
use -- and that's obvious, because it's a macro system that expresses
much more information (nested structures vs flat strings). Hygienic
macros are "more complicated" than `defmacro's in the same way -- they
add the concept of lexical scope. I wouldn't be surprised if going
through a lower level system puts you at a disadvantage similar to
what you get when you go from C to a functional language.
> Eli Barzilay wrote:
>> Marco Maggi <ma...@maggi.invalid> writes:
>>
>> > 2. Learn the syntax-case library.
>>
>> (`syntax-case' is not a library, it's just a way to deal with syntax
>> values via pattern matching. (And I hope that you don't use "library"
>> as in "learn the implementation" -- that would be an overkill.))
>
> (rnrs syntax-case) is one of the standard R6RS libraries.
I was talking about `syntax-case', not (rnrs syntax-case).
> Marco Maggi <ma...@maggi.invalid> writes:
>
>> People have difficulties in learning Scheme's hygienic macros, it
>> is a fact for me. Even if, at some point in your life, you are
>> comfortable in using an "ordinary" preprocessor (like GNU m4), IMHO
>> there is very little you can reuse in your brain from past
>> experiences in non-esoteric languages: Scheme hygienic macros are
>> on another level.
>
> Not another level: another language.
>
> That's what's so great with Common Lisp macros: they're written in
> Common Lisp.
Bogus point -- it's an illusion that was advocated in the pre r6rs
days when there was no standard low level macro system. You can see
in my reply that: Racket macros are written in Racket (and with r6rs
that's true for other implementations as well).
> On 17/03/2011 22:39, Thomas Munro wrote:
>> On Mar 16, 2:57 am, "Aaron W. Hsu"<arcf...@sacrideo.us> wrote:
>>> Indeed, any Scheme that supports syntax-case can have a form of defmacro.
>>
>> Is the opposite true? Can syntax-case be implemented on top of
>> defmacro?
>
> This can be answered with a strong and resounding "maybe". ;)
>
> See http://dx.doi.org/10.3217/jucs-016-02-0271
That's using CL's `defmacro', more than what's implemented in many
schemes. (And, IIUC, it requires redefining `lambda' etc; perhaps
that's the reason for your "maybe".)
> "Aaron W. Hsu" <arc...@sacrideo.us> writes:
>
>> [...] Racket is less forgiving in certain areas related to macro
>> systems (though not its macro system proper) than Chez is, including
>> libraries and phasing.
>
> This would be similar to saying that Scheme is less forgiving than
> JavaScript is when it comes to adding strings -- it throws an error
> for (+ 1 "2"), instead of returning "12". IMO (note the "M") think
> that "less forgiving" in both cases is desirable in avoiding confusing
> bugs.
Of course, I did not mean to advocate any particular approach to this in
my post. I simply wanted to highlight the differences in the two systems
in a general sense. This seems to be a point of confusion for people
learning the syntax-case macro system, when they see an example in one
system, like Larceny, or Chez, or Racket, and then they try it in another,
and it almost works, but not quite.
When learning syntax-case, it is important to remember that even among
some of the popular R6RS systems, there are some subtle variations in
semantics that are allowed by the R6RS standard, or that implementations
simply disregard. I think phasing, especially implicit versus explicit
phasing, tends to trip up people who are trying to test out snippets that
are written for various different implementations. So, to anyone who is
learning by playing with snippets and testing the out on their own system,
take a moment to learn whether your system is an implicit (Chez) or
explicit (Racket) phasing system, and what that means (a learning
experience in itself), and you will not be surprised by strange errors. :-)
Granted, in the simple syntax-case examples, this should not be a problem.
> Is the opposite true? Can syntax-case be implemented on top of
> defmacro?
No. Given a sufficiently expressive variation of Explicit Renaming or
Syntactic Closure macro systems, I am not convinced that syntax-case is
strictly more powerful than either of them (and I have tried moderately
hard to determine whether this is the case or not). ER macros very closely
resemble CL-style DEFMACRO macros in overall feel, with some important
differences (especially if you want to be as expressive as syntax-case),
but they have fundamental differences.
In particular, the syntax-case macro system actually combines a core
syntax data structure encapsulating s-expressions together with additional
lexical information (and, potentially, other source annotations) with a
set of forms and procedures for manipulating those structures, all of
which is integrated into a recursive process for transforming arbitrary
syntax into a restricted, "core form" syntax language. At a high level,
both DEFMACRO and syntax-case are systems for transforming source macro
expressions into target expressions, but the fundamental data structures
differ between the two systems. DEFMACRO uses the standard s-exprea
particular expansion process involvingssion, which inherently encodes less
information than the syntax-case data structure.
The well-known papers on hygienic macro systems do a good job of
illustrating why hygienic macro systems that are low-level and, in
particular, that can bend hygiene can express things which are not
possible to express using DEFMACRO.
The major part of the paper actually defines a new macro system from
scratch, in Scheme. (Yes, essential elements are based on what Common
Lisp's defmacro offers, and there is a section that describes how to
actually use Common Lisp to implement the hygiene-compatible macros, but
it's not narrowed down to Common Lisp only.)
> (And, IIUC, it requires redefining `lambda' etc; perhaps
> that's the reason for your "maybe".)
No, the reason for my "maybe" is that syntax-case has actually not been
implemented yet on top of what's in the paper.
And there is also a smiley there... ;)
A couple of years ago I wrote a very long guide to R6RS macros for people knowing defmacro:
http://www.artima.com/weblogs/viewpost.jsp?thread=251474
(there is Python in the title but you can read it whatever your dynamic language of choice is). Notice that it is not an idiot guide, and it goes into some length explaining how the R6RS module system works, the differences between implementations and the portability issues.