Improve intro to the concept of phases, fix some scribble typos, reformat.
This commit is contained in:
parent
acfe585c93
commit
cd4121548e
|
@ -6,26 +6,33 @@
|
|||
|
||||
@title[#:tag "phases"]{General Phase Levels}
|
||||
|
||||
A @deftech{phase} can be thought of as a way to separate
|
||||
computations. Imagine starting two Racket processes. If you ignore
|
||||
socket communication and the filesystem, the processes will have no
|
||||
way to share anything. Similarly, Racket effectively allows multiple
|
||||
invocations of a module to exist in the same process but separated by
|
||||
phase. Similar to socket communication for processes, module instances
|
||||
separated by phases can communicate only through the protocol of macro
|
||||
expansion.
|
||||
A @deftech{phase} can be thought of as a way to separate computations in
|
||||
a pipeline of processes where one produces code that is used by the
|
||||
next. (E.g., a pipeline that consists of a preprocessor process, a
|
||||
compiler, and an assembler.)
|
||||
|
||||
Imagine starting two Racket processes for this purpose. If you ignore
|
||||
inter-process communication channels like sockets and files, the
|
||||
processes will have no way to share anything other than the text that is
|
||||
piped from the standard output of one process into the standard input of
|
||||
the other. Similarly, Racket effectively allows multiple invocations of
|
||||
a module to exist in the same process but separated by phase. Racket
|
||||
enforces @emph{separation} of such phases, where different phases cannot
|
||||
communicate in any way other than via the protocol of macro expansion,
|
||||
where the output of one phases is the code used in the next.
|
||||
|
||||
@section{Phases and Bindings}
|
||||
|
||||
Every binding of an identifier exists in a particular phase. The link between
|
||||
a binding and its phase is represented by an integer @deftech{phase
|
||||
level}. Phase level 0 is the phase used for ``plain'' definitions, so
|
||||
Every binding of an identifier exists in a particular phase. The link
|
||||
between a binding and its phase is represented by an integer
|
||||
@deftech{phase level}. Phase level 0 is the phase used for ``plain''
|
||||
(or ``runtime'') definitions, so
|
||||
|
||||
@racketblock[
|
||||
(define age 5)
|
||||
]
|
||||
|
||||
adds a binding for @racket[age] into phase level 0. The identifier
|
||||
adds a binding for @racket[age] into phase level 0. The identifier
|
||||
@racket[age] can be defined at a higher phase level using
|
||||
@racket[begin-for-syntax]:
|
||||
|
||||
|
@ -35,13 +42,13 @@ adds a binding for @racket[age] into phase level 0. The identifier
|
|||
]
|
||||
|
||||
With a single @racket[begin-for-syntax] wrapper, @racket[age] is
|
||||
defined at phase level 1. We can easily mix these two definitions in
|
||||
the same module or top-level namespace, and there is no clash between
|
||||
the two @racket[age]'s that are defined at different phase levels:
|
||||
defined at phase level 1. We can easily mix these two definitions in
|
||||
the same module or in a top-level namespace, and there is no clash
|
||||
between the two @racket[age]s that are defined at different phase
|
||||
levels:
|
||||
|
||||
@(define age-eval (make-base-eval))
|
||||
@(interaction-eval #:eval age-eval
|
||||
(require (for-syntax racket/base)))
|
||||
@(interaction-eval #:eval age-eval (require (for-syntax racket/base)))
|
||||
|
||||
@interaction[#:eval age-eval
|
||||
(define age 3)
|
||||
|
@ -52,30 +59,31 @@ the two @racket[age]'s that are defined at different phase levels:
|
|||
The @racket[age] binding at phase level 0 has a value of 3, and the
|
||||
@racket[age] binding at phase level 1 has a value of 9.
|
||||
|
||||
Syntax objects capture binding information as a first-class value. Thus,
|
||||
Syntax objects capture binding information as a first-class value.
|
||||
Thus,
|
||||
|
||||
@racketblock[#'age]
|
||||
|
||||
is a syntax object that represents the @racket[age] binding---but
|
||||
since there are two @racket[age]'s (one at phase level 0 and one at
|
||||
phase level 1), which one does it capture? In fact, Racket imbues
|
||||
since there are two @racket[age]s (one at phase level 0 and one at
|
||||
phase level 1), which one does it capture? In fact, Racket imbues
|
||||
@racket[#'age] with lexical information for all phase levels, so the
|
||||
answer is that @racket[#'age] captures both!
|
||||
answer is that @racket[#'age] captures both.
|
||||
|
||||
The relevant binding of @racket[age] captured by @racket[#'age] is
|
||||
determined when @racket[#'age] is eventually used. As an example, we
|
||||
bind @racket[#'age] to a pattern variable so we can use it in a
|
||||
template, and then we @racket[eval]utae the template: @margin-note*{We
|
||||
use @racket[eval] here to demonstrate phases, but see
|
||||
@secref["reflection"] for caveats about @racket[eval].}
|
||||
use @racket[eval] here to demonstrate phases, but see
|
||||
@secref["reflection"] for caveats about @racket[eval].}
|
||||
|
||||
@interaction[#:eval age-eval
|
||||
(eval (with-syntax ([age #'age])
|
||||
#'(displayln age)))
|
||||
]
|
||||
|
||||
The result is 3 because @racket[age] is used at phase 0 level. We can
|
||||
try again with the use of @racket[age] inside
|
||||
The result is @racket[3] because @racket[age] is used at phase 0 level.
|
||||
We can try again with the use of @racket[age] inside
|
||||
@racket[begin-for-syntax]:
|
||||
|
||||
@interaction[#:eval age-eval
|
||||
|
@ -84,18 +92,20 @@ try again with the use of @racket[age] inside
|
|||
(displayln age))))
|
||||
]
|
||||
|
||||
In this case, the answer is 9, because we are using @racket[age] at
|
||||
phase level 1 instead of 0 (i.e., @racket[begin-for-syntax] evaluates its
|
||||
expressions at phase level 1). So, you can see that we started with the
|
||||
same syntax object, @racket[#'age], and we were able to use it in two
|
||||
different ways: at phase level 0 and at phase level 1.
|
||||
In this case, the answer is @racket[9], because we are using
|
||||
@racket[age] at phase level 1 instead of 0 (i.e.,
|
||||
@racket[begin-for-syntax] evaluates its expressions at phase level 1).
|
||||
So, you can see that we started with the same syntax object,
|
||||
@racket[#'age], and we were able to use it in two different ways: at
|
||||
phase level 0 and at phase level 1.
|
||||
|
||||
A syntax object has a lexical context from the moment it first exists.
|
||||
A syntax objects that is provided from a module retains its lexical
|
||||
context, and so it references bindings in the source module, not the use context.
|
||||
The following example defines @racket[button] at phase
|
||||
level 0 bound to the value 0, while @racket[see-button] binds the
|
||||
syntax object for @racket[button] in module @racket[a]:
|
||||
A syntax object that is provided from a module retains its lexical
|
||||
context, and so it references bindings in the context of its source
|
||||
module, not the context of its use. The following example defines
|
||||
@racket[button] at phase level 0 and binds it to @racket[0], while
|
||||
@racket[see-button] binds the syntax object for @racket[button] in
|
||||
module @racket[a]:
|
||||
|
||||
@interaction[
|
||||
(module a racket
|
||||
|
@ -114,39 +124,39 @@ syntax object for @racket[button] in module @racket[a]:
|
|||
(require 'b)
|
||||
]
|
||||
|
||||
The result of the @racket[m] macro is the value of
|
||||
@racket[see-button], which is @racket[#'button] with the lexical
|
||||
context of the @racket[a] module. Even though there is another
|
||||
@racket[button] in @racket[b], the second @racket[button] will not
|
||||
confuse Racket, because the lexical context of @racket[#'button] is
|
||||
The result of the @racket[m] macro is the value of @racket[see-button],
|
||||
which is @racket[#'button] with the lexical context of the @racket[a]
|
||||
module. Even though there is another @racket[button] in @racket[b], the
|
||||
second @racket[button] will not confuse Racket, because the lexical
|
||||
context of @racket[#'button] (the value bound to @racket[see-button]) is
|
||||
@racket[a].
|
||||
|
||||
Note that @racket[see-button] is bound at phase level 1 by virtue of
|
||||
defining it with @racket[define-for-syntax]. Phase level 1 is needed
|
||||
because @racket[m] is a macro, so its body executes at one phase
|
||||
higher than the context of its definition. Since @racket[m] is defined
|
||||
at phase level 0, its body is at phase level 1, so any bindings
|
||||
referenced by the body must be at phase level 1.
|
||||
defining it with @racket[define-for-syntax]. Phase level 1 is needed
|
||||
because @racket[m] is a macro, so its body executes at one phase higher
|
||||
than the context of its definition. Since @racket[m] is defined at
|
||||
phase level 0, its body is at phase level 1, so any bindings referenced
|
||||
by the body must be at phase level 1.
|
||||
|
||||
@; ======================================================================
|
||||
|
||||
@section{Phases and Modules}
|
||||
|
||||
A @tech{phase level} is a module-relative concept. When importing from
|
||||
an other module via @racket[require], Racket lets us shift imported
|
||||
bindings to a different phase level than the original one:
|
||||
A @tech{phase level} is a module-relative concept. When importing from
|
||||
another module via @racket[require], Racket lets us shift imported
|
||||
bindings to a phase level that is different from the original one:
|
||||
|
||||
@racketblock[
|
||||
(require "a.rkt") @code:comment{import with no phase shift}
|
||||
(require (for-syntax "a.rkt")) @code:comment{shift phase by +1}
|
||||
(require "a.rkt") @code:comment{import with no phase shift}
|
||||
(require (for-syntax "a.rkt")) @code:comment{shift phase by +1}
|
||||
(require (for-template "a.rkt")) @code:comment{shift phase by -1}
|
||||
(require (for-meta 5 "a.rkt" )) @code:comment{shift phase by +5}
|
||||
(require (for-meta 5 "a.rkt" )) @code:comment{shift phase by +5}
|
||||
]
|
||||
|
||||
That is, using @racket[for-syntax] in @racket[require] means that all
|
||||
of the bindings from that module will have their phase levels increased by
|
||||
one. A binding that is @racket[defined] at phase level 0
|
||||
and imported with @racket[for-syntax] becomes a phase-level 1 binding:
|
||||
That is, using @racket[for-syntax] in @racket[require] means that all of
|
||||
the bindings from that module will have their phase levels increased by
|
||||
one. A binding that is @racket[define]d at phase level 0 and imported
|
||||
with @racket[for-syntax] becomes a phase-level 1 binding:
|
||||
|
||||
@interaction[
|
||||
(module c racket
|
||||
|
@ -171,8 +181,8 @@ Let's see what happens if we try to create a binding for the
|
|||
]
|
||||
|
||||
Now both @racket[button] and @racket[see-button] are defined at phase
|
||||
0. The lexical context of @racket[#'button] will know that there is a
|
||||
binding for @racket[button] at phase 0. In fact it seems like things
|
||||
0. The lexical context of @racket[#'button] will know that there is a
|
||||
binding for @racket[button] at phase 0. In fact, it seems like things
|
||||
are working just fine if we try to @racket[eval] @racket[see-button]:
|
||||
|
||||
@interaction[#:eval button-eval
|
||||
|
@ -187,10 +197,11 @@ Now, let's use @racket[see-button] in a macro:
|
|||
(m)
|
||||
]
|
||||
|
||||
Clearly @racket[see-button] is not defined at phase level 1, so we cannot refer to
|
||||
it inside the macro body. Let's try to use @racket[see-button] in another module by
|
||||
putting the button definitions in a module and importing it at phase level 1.
|
||||
Then, we will get @racket[see-button] at phase level 1:
|
||||
Clearly, @racket[see-button] is not defined at phase level 1, so we
|
||||
cannot refer to it inside the macro body. Let's try to use
|
||||
@racket[see-button] in another module by putting the button definitions
|
||||
in a module and importing it at phase level 1. Then, we will get
|
||||
@racket[see-button] at phase level 1:
|
||||
|
||||
@interaction[
|
||||
(module a racket
|
||||
|
@ -205,36 +216,36 @@ Then, we will get @racket[see-button] at phase level 1:
|
|||
(m))
|
||||
]
|
||||
|
||||
Racket says that @racket[button] is unbound now! When @racket[a] is imported at
|
||||
phase level 1, we have the following bindings:
|
||||
Racket says that @racket[button] is unbound now! When @racket[a] is
|
||||
imported at phase level 1, we have the following bindings:
|
||||
|
||||
@racketblock[
|
||||
button @#,elem{at phase level 1}
|
||||
button @#,elem{at phase level 1}
|
||||
see-button @#,elem{at phase level 1}
|
||||
]
|
||||
|
||||
So, the macro @racket[m] can see a binding for @racket[see-button] at
|
||||
phase level 1 and will return the @racket[#'button] syntax object,
|
||||
which refers to @racket[button] binding at phase level 1. But the use
|
||||
of @racket[m] is at phase level 0, and there is no @racket[button] at
|
||||
phase level 0 in @racket[b]. That is why @racket[see-button] needs to
|
||||
be bound at phase level 1, as in the original @racket[a]. In the original @racket[b], then,
|
||||
we have the following bindings:
|
||||
So the macro @racket[m] can see a binding for @racket[see-button] at
|
||||
phase level 1 and will return the @racket[#'button] syntax object, which
|
||||
refers to @racket[button] binding at phase level 1. But the use of
|
||||
@racket[m] is at phase level 0, and there is no @racket[button] at phase
|
||||
level 0 in @racket[b]. That is why @racket[see-button] needs to be
|
||||
bound at phase level 1, as in the original @racket[a]. In the original
|
||||
@racket[b], then, we have the following bindings:
|
||||
|
||||
@racketblock[
|
||||
button @#,elem{at phase level 0}
|
||||
button @#,elem{at phase level 0}
|
||||
see-button @#,elem{at phase level 1}
|
||||
]
|
||||
|
||||
In this scenario, we can use @racket[see-button] in the macro, since
|
||||
@racket[see-button] is bound at phase level 1. When the macro expands,
|
||||
@racket[see-button] is bound at phase level 1. When the macro expands,
|
||||
it will refer to a @racket[button] binding at phase level 0.
|
||||
|
||||
Defining @racket[see-button] with @racket[(define see-button
|
||||
#'button)] isn't inherently wrong; it depends on how we intend
|
||||
@racket[see-button] to be used. For example, we can arrange for
|
||||
@racket[m] to sensibly use @racket[see-button] because it puts it in a
|
||||
phase level 1 context using @racket[begin-for-syntax]:
|
||||
#'button)] isn't inherently wrong; it depends on how we intend to use
|
||||
@racket[see-button]. For example, we can arrange for @racket[m] to
|
||||
sensibly use @racket[see-button] because it puts it in a phase level 1
|
||||
context using @racket[begin-for-syntax]:
|
||||
|
||||
@interaction[
|
||||
(module a racket
|
||||
|
@ -252,7 +263,7 @@ phase level 1 context using @racket[begin-for-syntax]:
|
|||
]
|
||||
|
||||
In this case, module @racket[b] has both @racket[button] and
|
||||
@racket[see-button] bound at phase level 1. The expansion of the macro
|
||||
@racket[see-button] bound at phase level 1. The expansion of the macro
|
||||
is
|
||||
|
||||
@racketblock[
|
||||
|
@ -262,18 +273,19 @@ is
|
|||
|
||||
which works, because @racket[button] is bound at phase level 1.
|
||||
|
||||
Now, you might try to cheat the phase system by importing @racket[a] at both
|
||||
phase level 0 and phase level 1. Then you would have the following bindings
|
||||
Now, you might try to cheat the phase system by importing @racket[a] at
|
||||
both phase level 0 and phase level 1. Then you would have the following
|
||||
bindings
|
||||
|
||||
@racketblock[
|
||||
button @#,elem{at phase level 0}
|
||||
button @#,elem{at phase level 0}
|
||||
see-button @#,elem{at phase level 0}
|
||||
button @#,elem{at phase level 1}
|
||||
button @#,elem{at phase level 1}
|
||||
see-button @#,elem{at phase level 1}
|
||||
]
|
||||
|
||||
In that case you might expect that @racket[see-button] in a macro should
|
||||
work, but it doesn't:
|
||||
You might expect now that @racket[see-button] in a macro would work, but
|
||||
it doesn't:
|
||||
|
||||
@interaction[
|
||||
(module a racket
|
||||
|
@ -290,22 +302,23 @@ work, but it doesn't:
|
|||
]
|
||||
|
||||
The @racket[see-button] inside the @racket[m] macro comes from the
|
||||
@racket[(for-syntax 'a)] import. For the macro to work, there must be a @racket[button]
|
||||
at phase 0 bound, and there is such a binding implied by @racket[(require 'a)].
|
||||
However, @racket[(require 'a)] and @racket[(require (for-syntax
|
||||
'a))] are different instantiations of the same module. The @racket[see-button] at
|
||||
phase 1 only refers to the @racket[button] at phase level 1, not the @racket[button]
|
||||
bound at phase 0 from a different instantiation---even from the same source module.
|
||||
@racket[(for-syntax 'a)] import. For the macro to work, there must be a
|
||||
@racket[button] at phase 0 bound, and there is such a binding implied by
|
||||
@racket[(require 'a)]. However, @racket[(require 'a)] and
|
||||
@racket[(require (for-syntax 'a))] are @emph{different instantiations}
|
||||
of the same module. The @racket[see-button] at phase 1 only refers to
|
||||
the @racket[button] at phase level 1, not the @racket[button] bound at
|
||||
phase 0 from a different instantiation---even from the same source
|
||||
module.
|
||||
|
||||
Mismatches like the one above can easily show up when a macro tries to
|
||||
match literal bindings---using @racket[syntax-case] or
|
||||
@racket[syntax/parse].
|
||||
Mismatches like the one above can show up when a macro tries to match
|
||||
literal bindings---using @racket[syntax-case] or @racket[syntax-parse].
|
||||
|
||||
@interaction[
|
||||
(module x racket
|
||||
(require (for-syntax syntax/parse)
|
||||
(for-template racket/base))
|
||||
|
||||
|
||||
(provide (all-defined-out))
|
||||
|
||||
(define button 0)
|
||||
|
@ -318,12 +331,12 @@ match literal bindings---using @racket[syntax-case] or
|
|||
(module y racket
|
||||
(require (for-meta 1 'x)
|
||||
(for-meta 2 'x racket/base))
|
||||
|
||||
|
||||
(begin-for-syntax
|
||||
(define-syntax (m stx)
|
||||
(with-syntax ([out (make)])
|
||||
#'(process (0 out)))))
|
||||
|
||||
#'(process (0 out)))))
|
||||
|
||||
(define-syntax (p stx)
|
||||
(m))
|
||||
|
||||
|
@ -336,7 +349,7 @@ refers to @racket[button] bound at phase level 0 inside @racket[x] and
|
|||
at phase level 2 in @racket[y] from @racket[(for-meta 2 'x)]. The
|
||||
@racket[process] macro is imported at phase level 1 from
|
||||
@racket[(for-meta 1 'x)], and it knows that @racket[button] should be
|
||||
bound at phase level 1. When the @racket[syntax-parse] is executed
|
||||
bound at phase level 1. When the @racket[syntax-parse] is executed
|
||||
inside @racket[process], it is looking for @racket[button] bound at
|
||||
phase level 1 but it sees only a phase level 2 binding and doesn't
|
||||
match.
|
||||
|
@ -349,7 +362,7 @@ relative to @racket[x], and then we import it at phase level 1 in
|
|||
(module x racket
|
||||
(require (for-syntax syntax/parse)
|
||||
(for-template racket/base))
|
||||
|
||||
|
||||
(provide (all-defined-out))
|
||||
|
||||
(define button 0)
|
||||
|
@ -368,8 +381,8 @@ relative to @racket[x], and then we import it at phase level 1 in
|
|||
(begin-for-syntax
|
||||
(define-syntax (m stx)
|
||||
(with-syntax ([out (make)])
|
||||
#'(process (0 out)))))
|
||||
|
||||
#'(process (0 out)))))
|
||||
|
||||
(define-syntax (p stx)
|
||||
(m))
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user