[plt-scheme] eval and phases (v4.0.1.2)

From: Matthew Flatt (mflatt at cs.utah.edu)
Date: Wed Jun 25 14:55:06 EDT 2008

As of version 4.0.1.2 (now in SVN):

 1. Each namespace has a "base phase" that is used by `eval',
    `dynamic-require', etc., instead of always starting in phase 0.

 2. While compile-time expressions are evaluated (such as the body of a
    macro transformer or the right-hand side of `define-for-syntax'),
    the current namespace is set to one whose base phase matches the
    evaluation phase.

For example, running

  #lang scheme/load

  (define x 5)
  (define-for-syntax x 8)

  (begin-for-syntax
    (printf "~s\n" (eval 'x)))

now produces the output 8 instead of 5, since the expansion-time call
to `eval' uses a namespace whose base phase is 1.


This change fixes two specific problems:

 * Using deserialization at macro-expansion time didn't work before, as
   Danny noticed:
    http://list.cs.brown.edu/pipermail/plt-scheme/2008-February/022986.html

 * Catching R6RS exceptions from uses of `eval' at macro expansion time
   didn't work before, as Steve noticed:
    http://list.cs.brown.edu/pipermail/plt-scheme/2008-May/024712.html

More generally, `eval' no longer skips across phases boundaries. Unless
you find anther route from one phase to another, if you're running in
phase N, you'll only see namespaces with base phase N.

 [I know of two remaining ways to pass data across phases: via "3-D
  syntax', where a value is wrapped in a syntax object and embedded in
  an expression as a "constant", and via built-in parameters, such as a
  module-name resolver. Closing those holes is future work.]


As usual, the `module' form plays a crucial role in keeping everything
coherent. Suppose that you're expanding module M, and a macro
transformer used by expansion needs to compile another module N. (This
happens all the time when a `require' form is expanded and N is not
already compiled to bytecode.) The current namespace for compiling N
starts at base phase 1. When the `module' form starts expanding the
body of N, however, it creates a new namespace that is back at phase 0.

Globally, this works out because phase 1 for expanding N is distinct
from other phase 1 namespaces; in particular, macro transformers
running to expand N don't contaminate the (temporarily suspended)
expansion of M. Locally, it means that you can reason about modules and
phases the same as before.

 [If you're writing a load handler for `module' forms (few people do
  that), then you need to be careful to inject a `module' identifier
  with a binding in right phase, since the load handle could be
  triggered at an arbitrary phase. The new
  `namespace-module-identifier' function helps get such an identifier.]


I don't expect this change to create much trouble, but let me know if
you run into behavior that looks like a bug. It wasn't especially
difficult to implement the change, but there are more opportunities
than usual for me to get something wrong or to overlook
modes/combinations in testing.


Matthew



Posted on the users mailing list.