JRR
JRR

Reputation: 6152

calling other functions in macros

How can I call other functions/macros within a macro? The following doesn't seem to work (even if I define bar with define-syntax)

(define (bar) #'"hello")

(define-syntax (foo stx)
  (syntax-case stx ()
    [(_ '(a b)) (bar)]))

Upvotes: 4

Views: 439

Answers (1)

Alexis King
Alexis King

Reputation: 43842

Racket’s macro system maintains careful separation between runtime and compile-time code. You defined bar as a runtime function, but you actually want to use it in a macro. Therefore, you need to explicitly define bar at compile-time by wrapping it with begin-for-syntax:

(begin-for-syntax
  (define (bar) #'"hello"))

This will solve your problem. For more information, see Compile and Run-Time Phases in The Racket Guide.


Why is this necessary? Well, various reasons. First of all, by explicitly distinguishing runtime code from compile-time code, the compiler can make certain guarantees about when code is loaded. For example, you might use a library in the implementation of a macro, but you might never use the library at runtime. By taking care to separate runtime and compile-time, the compiler can be sure to only bother loading the library at compile-time, not at runtime.

In Racket, we call the different times that code can run phases, and we assign each phase a number. For example, runtime is phase 0 and compile-time is phase 1. Why bother using numbers? Well, it turns out there are more than just two phases! In fact, there can be an arbitrary number of compilation phases in Racket, continuing with phase 2, phase 3, and so on.

So if phase 1 is compile-time, what is phase 2? Well, what if you use a macro in the implementation of another macro? If you try directly, it won’t work:

(define-syntax (foo stx)
  (syntax-case stx ()
    [(_) #''foo]))

(define-syntax (bar stx)
  (syntax-case stx ()
    [(_) (foo)]))

Once again, the above program will complain that foo is unbound, since foo is defined at phase 0, but the code inside bar is at phase 1. Therefore, we need to wrap the definition of foo in begin-for-syntax, just like before:

(begin-for-syntax
  (define-syntax (foo stx)
    (syntax-case stx ()
      [(_) #''foo])))

But here’s the question: what phase is the code that implements foo at? It obviously isn’t phase 0, since it’s a macro, but it isn’t phase 1, either, since it’s a macro defined at compile-time (since it’s wrapped in begin-for-syntax). Therefore, the body of foo is at phase 2!

Indeed, if you try to write the above code, you may get some errors that lots of things are unbound. When you write #lang racket, things are automatically imported at phase 0 and phase 1, but in general, modules are only imported at individual phases as well. To make the above snippet work, we’d need to import racket/base at phase 2, like this:

(require (for-meta 2 racket/base))

All the details of phases are outside the scope of this answer, but the point I’m making is that phases in Racket are important, and when you’re writing macros, you have to worry about them. For a more thorough treatment, see General Phase Levels in The Racket Guide, which complements and extends the previously-linked introductory section. For even more details about why phase levels are important and what goes wrong when you don’t have phase separation, see the (very readable) first section of the paper Composable and Compilable Macros, which first introduced Racket’s module system.

Upvotes: 4

Related Questions