I'm skimming section 16.1 of "The Racket Guide" and I'm stuck on 16.1.4. I understand everything prior to 16.1.4 (of section 16.1 and not the whole guide). What does the following mean?
(define-syntax rotate
(syntax-rules ()
[(rotate a c ...)
(shift-to (c ... a) (a c ...))]))
(define-syntax shift-to
(syntax-rules ()
[(shift-to (from0 from ...) (to0 to ...))
(let ([tmp from0])
(set! to from) ...
(set! to0 tmp))]))
I understand that the "rotate" macro makes use of the "shift-to" macro. But how does a list "a c ..." become "(c ... a) (a c ...)"?
And then in the "shift-to" macro the whole thing is confusing. How does "(c ... a) (a c ...)" map to "(from0 from ...) (to0 to ...)"?
Why does the list become "(c ... a) (a c ...)" as it gets plugged into the "shift-to" macro? Why is it "(shift-to (c ... a) (a c ...)) and not "shift-to (a c ...) (c ... a)"?
Let's say I have a list '(1 2 3).
If I plug that into the "rotate" macro, then the "rotate" macro invokes the "shift-to" helper macro.
That part I understand.
What I don't understand is how it went from list '(1 2 3) to "(shift-to (2 3 1) (1 2 ...))". And why in that order?
And how does "(shift-to (2 3 1) (1 2 ...)" map to "(shift-to (from0 from ...) (to0 to ...)))".
I've tried using '(1 2 3) as an example and working it out on paper, but it's totally baffling.
There are no lists here; trying to pass a list (or number) to rotate
will ultimately cause errors because set!
expects a variable/identifier as its first argument. How it should be used:
(let ([x 1] [y 2] [z 3])
(printf "x = ~A y = ~A z = ~A~%" x y z)
(rotate x y z)
(printf "x = ~A y = ~A z = ~A~%" x y z))
In the pattern part of the macro, a
matches a single element, and c ...
matches 0 or more elements. In the body, a
is replaced by its matching element, and c ...
takes its elements, and for each one, expands the term before the ...
with c
bound to the current one. The result has all those expansions concatenated together.
So (rotate x y z)
expands to (shift-to (y z x) (x y z))
. a
was bound to x
, and c ...
to the sequence y z
.
shift-to
's body is more complicated, with multiple terms in the expression before the ...
(to
and from
) but the same basic idea applies. (shift-to (y z x) (x y z))
expands to
(let ([tmp y]) ; from0 is y; tmp is a unique variable distinct from any other with that name, including ones used as arguments to the macro
(set! y z) ; first pair of from and to values
(set! z x) ; second pair of from and to values
(set! x tmp))])) ; to0 is x
As an aside, though it's probably not appropriate for the Guide's basic introduction, I'd use syntax-parse
macros instead, to get a better error message when used with things other than variable names:
(require syntax/parse/define)
(define-syntax-parse-rule (rotate a:id c:id ...)
(shift-to (c ... a) (a c ...)))
(define-syntax-parse-rule (shift-to (from0:id from:id ...) (to0:id to:id ...))
(let ([tmp from0])
(set! to from) ...
(set! to0 tmp)))
Using the Guide's version:
> (rotate 1 2 3)
set!: not an identifier in: 2
Using mine:
> (rotate 1 2 3)
rotate: expected identifier in: 1
But save exploring them for later when you're more comfortable with the basic syntax-rules
macros.