http://www.ccs.neu.edu/scheme/pubs/scheme2007-ctf.pdf
On Thu, Mar 1, 2012 at 4:19 PM, Jon Rafkind <rafk...@cs.utah.edu> wrote: > 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 >> >> > -- sam th sa...@ccs.neu.edu ____________________ Racket Users list: http://lists.racket-lang.org/users