link On 03/01/2012 01:53 PM, Sam Tobin-Hochstadt wrote: > There's also something of a tutorial on phases in our Scheme Workshop > 2007 paper, some of which might be worth adding here. In particular, > it has some pictures/diagrams. > > On Thu, Mar 1, 2012 at 3:52 PM, Matthias Felleisen <matth...@ccs.neu.edu> > wrote: >> Nice job. Now polish and add this write-up to the guide. Thanks -- Matthias >> >> >> On Mar 1, 2012, at 3:31 PM, Jon Rafkind wrote: >> >>> Recent problems with phases have led me to investigate how they work in >>> more detail. Here is a brief tutorial on what they are and how they work >>> with macros. The guide and reference have something to say about phases but >>> I don't think they go into enough detail. >>> >>> Bindings exist in a phase. The link between a binding and its phase is >>> represented by an integer. Phase 0 is the phase used for "plain" >>> definitions, so >>> >>> (define x 5) >>> >>> Will put a binding for 'x' into phase 0. 'x' can be defined at higher >>> phases easily >>> >>> (begin-for-syntax >>> (define x 5)) >>> >>> Now 'x' is defined at phase 1. We can easily mix these two definitions in >>> the same module, there is no clash between the two x's because they are >>> defined at different phases. >>> >>> (define x 3) >>> (begin-for-syntax >>> (define x 9)) >>> >>> 'x' at phase 0 has a value of 3 and 'x' at phase 1 has a value of 9. >>> >>> Syntax objects can refer to these bindings, essentially they capture the >>> binding as a value that can be passed around. >>> >>> #'x >>> >>> Is a syntax object that represents the 'x' binding. But which 'x' binding? >>> In the last example there are two x's, one at phase 0 and one at phase 1. >>> Racket will imbue #'x with lexical information for all phases, so the >>> answer is both! >>> >>> Racket knows which 'x' to use when the syntax object is used. I'll use eval >>> just for a second to prove a point. >>> >>> First we bind #'x to a pattern variable so we can use it in a template and >>> then just print it. >>> (eval (with-syntax ([x #'x]) >>> #'(printf "~a\n" x))) >>> >>> This will print 3 because x at phase 0 is bound to 3. >>> >>> (eval (with-syntax ([x #'x]) >>> #'(begin-for-syntax >>> (printf "~a\n" x)))) >>> >>> This will print 9 because we are using x at phase 1 instead of 0. How does >>> Racket know we wanted to use x at phase 1 instead of 0? Because of the >>> 'begin-for-syntax'. So you can see that we started with the same syntax >>> object, #'x, and was able to use it in two different ways -- at phase 0 and >>> at phase 1. >>> >>> When a syntax object is created its lexical context is immediately set up. >>> When a syntax object is provided from a module its lexical context will >>> still reference the things that were around in the module it came from. >>> >>> This module will define 'foo' at phase 0 bound to the value 0 and 'sfoo' >>> which binds the syntax object for 'foo'. >>> >>> ;; a.rkt >>> (define foo 0) >>> (provide (for-syntax sfoo)) >>> (define-for-syntax sfoo #'foo) >>> ;; why not (define sfoo #'foo) ? I will explain later >>> >>> ;; b.rkt >>> (require "q.rkt") >>> (define foo 8) >>> (define-syntax (m stx) >>> sfoo) >>> (m) >>> >>> The result of the (m) macro will be whatever value 'sfoo' is bound to, >>> which is #'foo. The #'foo that 'sfoo' knows that 'foo' is bound from the >>> a.rkt module at phase 0. Even though there is another 'foo' in b.rkt this >>> will not confuse Racket. >>> >>> Note that 'sfoo' is bound at phase 1. This is because (m) is a macro so its >>> body executes at one phase higher than it was defined at. Since it was >>> defined at phase 0 it will execute at phase 1, so any bindings it refers to >>> also need to be bound at phase 1. >>> >>> Now really what I want to show is how bindings can be confused when modules >>> are imported at different phases. Racket allows us to import a module at an >>> arbitrary phase using require. >>> >>> (require "a.rkt") ;; import at phase 0 >>> (require (for-syntax "a.rkt")) ;; import at phase 1 >>> (require (for-template "a.rkt")) ;; import at phase -1 >>> (require (for-meta 5 "a.rkt" )) ;; import at phase 5 >>> >>> What does it mean to 'import at phase 1'? Effectively it means that all the >>> bindings from that module will have their phase increased by one. >>> >>> ;; c.rkt >>> (define x 0) ;; x is defined at phase 0 >>> >>> ;; d.rkt >>> (require (for-syntax "c.rkt")) >>> >>> Now in d.rkt there will be a binding for 'x' at phase 1 instead of phase 0. >>> >>> So lets look at a.rkt from above and see what happens if we try to create a >>> binding for the #'foo syntax object at phase 0. >>> >>> ;; a.rkt >>> (define foo 0) >>> (define sfoo #'foo) >>> (provide sfoo) >>> >>> Now both 'foo' and 'sfoo' are defined at phase 0. The lexical context of >>> #'foo will know that there is a binding for 'foo' at phase 0. In fact it >>> seems like things are working just fine, if we try to eval sfoo in a.rkt we >>> will get 0. >>> >>> (eval sfoo) >>> --> 0 >>> >>> But now lets use sfoo in a macro. >>> >>> (define-syntax (m stx) >>> sfoo) >>> (m) >>> >>> We get an error 'reference to an identifier before its definition: sfoo'. >>> Clearly 'sfoo' is not defined at phase 1 so we cannot refer to it inside >>> the macro. Lets try to use 'sfoo' in another module by importing a.rkt at >>> phase 1. Then we will get 'sfoo' at phase 1. >>> >>> ;; b.rkt >>> (require (for-syntax "a.rkt")) ;; now we have sfoo at phase 1 >>> (define-syntax (m stx) >>> sfoo) >>> (m) >>> >>> $ racket b.rkt >>> compile: unbound identifier (and no #%top syntax transformer is bound) in: >>> foo >>> >>> Racket says that 'foo' is unbound now. When 'a.rkt' is imported at phase 1 >>> we have the following bindings >>> >>> foo at phase 1 >>> sfoo at phase 1 >>> >>> So the macro 'm' can see sfoo and will return the #'foo syntax object which >>> knows that 'foo' was bound at phase 0. But there is no 'foo' at phase 0 in >>> b.rkt, there is only a 'foo' at phase 1, so we get an error. That is why >>> 'sfoo' needed to be bound at phase 1 in a.rkt. In that case we would have >>> had the following bindings after doing (require "a.rkt") >>> >>> foo at phase 0 >>> sfoo at phase 1 >>> >>> So we can still use 'sfoo' in the macro since its bound at phase 1 and when >>> the macro finishes it will refer to a 'foo' binding at phase 0. >>> >>> If we import a.rkt at phase 1 we can still manage to use 'sfoo'. The trick >>> is to create a syntax object that will be evaluated at phase 1 instead of >>> 0. We can do that with 'begin-for-syntax'. >>> >>> ;; a.rkt >>> (define foo 0) >>> (define sfoo #'foo) >>> (provide sfoo) >>> >>> ;; b.rkt >>> (require (for-syntax "a.rkt")) >>> (define-syntax (m stx) >>> (with-syntax ([x sfoo]) >>> #'(begin-for-syntax >>> (printf "~a\n" x)))) >>> (m) >>> >>> b.rkt has 'foo' and 'sfoo' bound at phase 1. The output of the macro will be >>> >>> (begin-for-syntax >>> (printf "~a\n" foo)) >>> >>> Because 'sfoo' will turn into 'foo' when the template is expanded. Now this >>> expression will work because 'foo' is bound at phase 1. >>> >>> Now you might try to cheat the phase system by importing a.rkt at both >>> phase 0 and phase 1. Then you would have the following bindings >>> >>> foo at phase 0 >>> sfoo at phase 0 >>> foo at phase 1 >>> sfoo at phase 1 >>> >>> So just using sfoo in a macro should work >>> >>> ;; b.rkt >>> (require "a.rkt" >>> (for-syntax "a.rkt")) >>> (define-syntax (m stx) >>> sfoo) >>> (m) >>> >>> The 'sfoo' inside the 'm' macro comes from the (for-syntax "a.rkt"). For >>> this macro to work there must be a 'foo' at phase 0 bound, and there is one >>> from the plain "a.rkt" imported at phase 0. But in fact this macro doesn't >>> work, it says 'foo' is unbound. The key is that "a.rkt" and (for-syntax >>> "a.rkt") are different instantiations of the same module. The 'sfoo' at >>> phase 1 only knows that about 'foo' at phase 1, it does not know about the >>> 'foo' bound at phase 0 from a different instantiation, even from the same >>> file. >>> >>> So this means that if you have a two functions in a module, one that >>> produces a syntax object and one that matches on it (say using >>> syntax/parse) the module needs to be imported once at the proper phase. The >>> module can't be imported once at phase 0 and again at phase 1 and be >>> expected to work. >>> >>> ;; x.rkt >>> #lang racket >>> >>> (require (for-syntax syntax/parse) >>> (for-template racket/base)) >>> >>> (provide (all-defined-out)) >>> >>> (define foo 0) >>> (define (make) #'foo) >>> (define-syntax (process stx) >>> (define-literal-set locals (foo)) >>> (syntax-parse stx >>> [(_ (n (~literal foo))) #'#''ok])) >>> >>> ;; y.rkt >>> #lang racket >>> >>> (require (for-meta 1 "q6.rkt") >>> (for-meta 2 "q6.rkt" racket/base) >>> ;; (for-meta 2 racket/base) >>> ) >>> >>> (begin-for-syntax >>> (define-syntax (m stx) >>> (with-syntax ([out (make)]) >>> #'(process (0 out))))) >>> >>> (define-syntax (p stx) >>> (m)) >>> >>> (p) >>> >>> $ racket y.rkt >>> process: expected the identifier `foo' at: foo in: (process (0 foo)) >>> >>> 'make' is being used in y.rkt at phase 2 and returns the #'foo syntax >>> object which knows that foo is bound at phase 0 inside y.rkt, and at phase >>> 2 from (for-meta 2 "q6.rkt"). The 'process' macro is imported at phase 1 >>> from (for-meta 1 "q6.rkt") and knows that foo should be bound at phase 1 so >>> when the syntax-parse is executed inside 'process' it is looking for 'foo' >>> bound at phase 1 but it sees a phase 2 binding and so doesn't match. >>> >>> To fix this we can provide 'make' at phase 1 relative to x.rkt and just >>> import it at phase 1 in y.rkt >>> >>> ;; x.rkt >>> #lang racket >>> >>> (require (for-syntax syntax/parse) >>> (for-template racket/base)) >>> >>> (provide (all-defined-out)) >>> >>> (define foo 0) >>> (provide (for-syntax make)) >>> (define-for-syntax (make) #'foo) >>> (define-syntax (process stx) >>> (define-literal-set locals (foo)) >>> (syntax-parse stx >>> [(_ (n (~literal foo))) #'#''ok])) >>> >>> ;; y.rkt >>> #lang racket >>> >>> (require (for-meta 1 "q6.rkt") >>> ;; (for-meta 2 "q6.rkt" racket/base) >>> (for-meta 2 racket/base) >>> ) >>> >>> (begin-for-syntax >>> (define-syntax (m stx) >>> (with-syntax ([out (make)]) >>> #'(process (0 out))))) >>> >>> (define-syntax (p stx) >>> (m)) >>> >>> (p) >>> >>> $ racket y.rkt >>> 'ok >>> ____________________ >>> Racket Users list: >>> http://lists.racket-lang.org/users >> >> ____________________ >> Racket Users list: >> http://lists.racket-lang.org/users > >
____________________ Racket Users list: http://lists.racket-lang.org/users