Search code examples
macrosracket

Racket "rotate" and "shift-to" macro from "The Racket Guide"


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.


Solution

  • 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.