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

Reply via email to