dhCompiler
dhCompiler

Reputation: 51

how to use with-syntax macro

I'm a new people to Scheme, when reading the SICP, I found:

->I need to read the "The Scheme programming language 4",

-->I need to read the r6rs,

--->I read "Yet another Scheme tutorial",

--->I need to read the "Writing Hygienic Macros in Scheme with Syntax-Case".

When reading the last one, I try:

(define-syntax with-syntax1 ;;;racket has a with-syntax
  (lambda (x)
    (syntax-case x ()
      ((_ ((p e0) ...) e1 e2 ...)
       (syntax (syntax-case (list e0 ...) ()
                 ((p ...) (begin e1 e2 ...))))))))

(define-syntax or1
  (lambda (x)
    (syntax-case x ()
      ((_) (syntax #f))
      ((_ e) (syntax e))
      ((_ e1 e2 e3 ...)
       (with-syntax1 ((rest (syntax (or e2 e3 ...))))
         (syntax (let ((t e1)) (if t t rest))))))))

I got a error: rest: unbound identifier in module (in phase 1, transformer environment) in: rest

//----------------------------------------------------------------

When using racket's "with-syntax" to define another or:

(define-syntax or
  (lambda (x)
    (syntax-case x ()
      ((_) (syntax #f))
      ((_ e) (syntax e))
      ((_ e1 e2 e3 ...)
       ;;;use racket's with-syntax
       (with-syntax ((rest (syntax (or e2 e3 ...))))
         (syntax (let ((t e1)) (if t t rest))))))))

call it as (or 1 2), the call will never end.

//---the root cause of the second problem had been found---------------------

My question is:

What's the problem there in the above two "or".

Is there any road-map(or, book list, one by one) I can follow to learn the Scheme/Racket?

I'm very interesting about the "hygienic macro" in Scheme, I want to learn how to write a macro, and, I also want to know the theory behind the hygienic macro.

Upvotes: 4

Views: 945

Answers (2)

Ryan Culpepper
Ryan Culpepper

Reputation: 10653

The first error is due to a phase mismatch. Racket uses phases to tell what code needs to be executed at compile time (ie, macro expansion time) vs run time. Phases are necessary to get predictable and repeatable compilation in a language with both macros and side-effects.

In your macro, with-syntax1 is used in the macro transformer to compute the result syntax, so the definition of with-syntax1 must occur at compile time (with respect to the top level). Here's the first step to repairing your program:

(begin-for-syntax
  (define-syntax with-syntax1 (lambda (x) ___)))
(define-syntax or1 ___)

If you run that, though, you get an error about lambda being unbound at phase 2. That's because lambda is used in a macro transformer defined at compile time, so it must be available at compile time's compile time! That is, there aren't just two phases; there can be many levels of "compile time". We label "run time" as phase 0, "compile time" as phase 1, "compile time's compile time" as phase 1, and so on.

Here's a full repair to your example:

(require racket/base               ;; phase 0
         (for-syntax racket/base)  ;; phase 1
         (for-meta 2 racket/base)) ;; phase 2

(begin-for-syntax                   ;; A
  (define-syntax with-syntax1       ;; B
    (lambda (x)                     ;; C
      (syntax-case x ()
        ((_ ((p e0) ...) e1 e2 ...)
         (syntax
          (syntax-case (list e0 ...) () ;; D
            ((p ...) (begin e1 e2 ...)))))))))

(define-syntax or1                  ;; E
  (lambda (x)                       ;; F
    (syntax-case x ()
      ((_) (syntax #f))
      ((_ e) (syntax e))
      ((_ e1 e2 e3 ...)
       (with-syntax1 ((rest (syntax (or e2 e3 ...)))) ;; G
         (syntax (let ((t e1)) (if t t rest))))))))

Here are some notes on the bindings and phases in this program:

  • On line A, begin-for-syntax is a reference at phase 0 to a special form that shifts its body one phase higher (so the body is at phase 1).
  • On line B, define-syntax is a reference at phase 1 bound by (require (for-syntax racket/base)); it causes with-syntax1 to be defined as a macro at phase 1, and the transformer expression is at phase 2.
  • On line C, lambda is a reference at phase 2 bound by (require (for-meta 2 racket/base)).
  • On line D, syntax-case occurs within a syntax template, which does not count as using it in the macro. It isn't a reference yet, but it will produce a reference when the with-syntax macro is used. The with-syntax1 macro is defined at phase 1, so it must be used at phase 1, which means the reference to syntax-case it produces must be bound at phase 1. And it is bound, by the second require.
  • For homework, label lines E, F, and G :)

There are other ways to structure a program like this. One is to put with-syntax1 in another module and require it for-syntax:

(module with-syntax1-mod _) (module or1-mod _ (require (for-syntax 'with-syntax-mod)) ___)

The constraints imposed by phasing stay the same, but you must be careful in labeling the phases now: "phase 1 with respect to with-syntax1-mod" is the same phase as "phase 2 with respect to or1-mod" because it is required for-syntax (ie, at phase +1).

I recommend reading the macros section of the Racket Guide, particularly the later parts on phases. Fear of Macros is also good for demystifying some of the issues around macros.

Upvotes: 3

John Clements
John Clements

Reputation: 17203

The problem with your second example is simple: you need a space between the _ and the e or e1. Viz:

(define-syntax or
  (lambda (x)
    (syntax-case x ()
      ((_) (syntax #f))
      ((_ e) (syntax e))
      ((_ e1 e2 e3 ...)
       ;;;use racket's with-syntax
       (with-syntax ((rest (syntax (or e2 e3 ...))))
         (syntax (let ((t e1)) (if t t rest))))))))

(or 1 2)

To see why it ran forever: your macro matched the identifier _e1 with the identifier or, and e2 with 1, and e2 ... with (2), so the macro expanded into syntax that had nested within it... (or 1 2), leading to infinite expansion.

Upvotes: 1

Related Questions