[plt-scheme] Attaching compile time information to identifiers

Jens Axel Søgaard jensaxel at soegaard.net
Sun Oct 29 15:31:10 EST 2006


David Van Horn skrev:
> I'd like to write two macros, f & g, with the following usage:
> 
> (f id e ...)
> (g id)
> 
> Such that for the same identifier id, g knows (during expansion) how 
> many e's were given in (f id e ...), ie. I'd like f to attach some 
> information to id that g can subsequently look up.  How can I do this?

Start with these slides

   "http://www.cs.utah.edu/plt/slideshow/macro-module-slides/mmtalk.pdf"

and then go on to the paper:

   "Composable and Compilable Macros - You Want it When?".
   Matthew Flatt, ICFP 2002

My attempt at explaining the concepts via a simpler example
than the record one is found in the attached note. The exact
same technique can be used for your macros.

Simply cut and paste all modules into the definition window
and hit run.

-- 
Jens Axel Søgaard
-------------- next part --------------
AN EXAMPLE OF MACROS AND MODULES (ver 3) -- Jens Axel Søgaard
---------------------------------------------------------------------------------

Note: I tried to think up a simple example. Suggestions for making
      simplifications are welcome.

      A real-world example of these ideas can be found in the PLT
      implementation of the EOPL's define-datatypes and cases,
      which is two macros that work together. 
      (See <http://download.plt-scheme.org/scheme/plt/collects/eopl/datatype.ss>)

See [Flatt] for a more elaboarate explanation of goals and phases.

   [Flatt] "Composable and Compilable Macros - You Want it When?". 
           Matthew Flatt, ICFP 2002

GOAL 1
------

An important goal for a module system that supports both interactive
and compiles use is:

   1) The meaning of a code fragment does not depend on the order in
      which code is compiled and executed.

This is an important goal because:

   i) A programmer can think of the module system as a way to specify
      which language the code of the module should be written in. A
      "language" should in this context be thought of as a set of
      visible bindings for both values and syntax.

  ii) Users of a module does not need to know how anything about the
      dependencies of the included module.

 iii) If the program works in an interactive environment, it will also
      work when compiled.

GOAL 2
------

The second goal is:

   2) All errors that can be checked statically should be detected at
      compile-time.

The rationale for this goal is:

   i) Static checking provides better error messages than run-time
      errors, since errors can inform of the programmer of the
      syntactical location of the error. This saves programmer time.

  ii) Things checked at compile-time need not be checked at run-time. 
      Reducing the number of run-time checks. This leads to faster
      programs. 

[NOTE: Macro writers should respect goal 2) when they write their own
       macros. ]


PROBLEMS IN ACHIEVING THE GOAL
------------------------------

The following is an attempt to show the difficulties of achieving both
goal at the same time in the presence of macros.

The difficult thing to get right for module system is the handling of
the compile-time environment. The compile time environment is used
by macros to share information between different macros. Information
shared between macros is needed to to do proper static checking.

As an concrete example we will look at the implementation of two forms:

    (define-constant name value)
    (constant name)

The (define-constant name value) register that name is a constant, 
and (constant name) expands to the value.

Two things should be checked at compile-time:

  a) Only names defined by define-constant can be used by constant.
  b) A name can only be defined by define-constant once.


An usage example:

  > (define-constant root2 (sqrt 2))
  > (constant root2)
  1.4142135623730951
  > (define e 2.71828)
  > (constant e)
  error: e not defined as constant
  > (define-constant root2 1.1415)
  error: define-constant: constant already defined; root2  

Here is the first attempt:


;;; compile-time.scm
(module compile-time mzscheme
  (provide register-constant 
           lookup-constant
           constant-registered?
           constants
           syntax-error)
  
  ; list of registered constants
  (define constants '())
  ; register a constant
  (define (register-constant name value)
    (set! constants (cons (cons name value) constants)))
  ; check if a constant is registered
  (define (constant-registered? name)
    (if (assoc name constants) #t #f))
  ; lookup the value associated with the name of a name
  (define (lookup-constant name error-thunk)
    (let ((a (assoc name constants)))
      (if a (cdr a) (error-thunk))))
  ; helper for signaling syntax errors
  (define (syntax-error message . values)
    (apply raise-syntax-error #f message values)))


;;; constants.scm
(module constants mzscheme
  (provide define-constant
           constant)
  
  (require-for-syntax compile-time)
  
  (define-syntax (define-constant stx)
    (syntax-case stx ()
      ((_ name datum)
       (begin
         (let ([constant-name  (syntax-object->datum #'name)]
               [constant-value (syntax-object->datum #'datum)])
           ;; Register the constant name and value for this compilation,
           ;; but first check that the constant hasn't been defined before
           (if (constant-registered? constant-name)
               (syntax-error "define-constant: can't redefine constant: " (syntax name))
               (register-constant constant-name constant-value)  ; <- compile-time of constants.scm
               )

           ;; The result is simply the invisible value
           #'(void))))))
           
                                
  (define-syntax (constant stx)
    (syntax-case stx ()
      ((_ name)
       (let* ([constant-name  (syntax-object->datum #'name)]
              [constant-value (lookup-constant 
                               constant-name 
                               (lambda ()
                                 (syntax-error "constant: unknown constant; " (syntax name))))])
         (datum->syntax-object #'stx constant-value)))))
  )


And now let us try it:

> (require constants)

> (constant pi/2)
[bug] pi/2: constant: unknown constant;  in: pi/2

> (define-constant pi/2  (* 2 (atan 1)))
> (constant pi/2)
1.5707963267948966

> (define-constant pi/2 4)
[bug] pi/2: define-constant: can't redefine constant:  in: pi/2

Seems to work fine, right?

Let's see what happens when modules enter the picture: 

(module roots mzscheme
  (require constants)
  (define-constant root2 (sqrt 2))
  (define-constant root3 (sqrt 3)))
 
> (require constants)
> (require roots)
> (constant root2)
root2: constant: unknown constant;  in: root2


Why did this happen? During the compilation of constants.scm
the constants are registered with the help of the operations
in compile-time.scm among these operations are constant-registered? .
When the compilation of constants.scm is finished, the imported
values and functions from compile-time.scm disappears. When
roots.scm is compiled, the values used during compilation of
constants.scm is needed again, but the compiler doesn't ensure
that these are revived. But we can't really blame the compiler - 
how should it know what to reinstate?

Let's take a look at the conclusion in [Flatt]:

    "A language that allows macro transformers to perform arbitrary
    computation must enforce a separation between computations: run
    time versus compile-time, as well as the compile-time of one
    module versus the compile time of another. Without an enforced
    separation, the meaning of a code fragment can depend on the order
    in which code is compiled and executed."

The key here is "as well as the compile-time of one module versus the
compile time of another.". To handle this Flatt introduces phases.
Furthermore the problem of what to reinstate is solved by spliting
the normal environment and the syntactical environment.

The solution is to change the definition of define-constant
so that it registers the definitions also at compile-time for
other modules. 

;;; constants.scm
(module constants mzscheme
  (provide define-constant
           constant)
  
  (require-for-syntax compile-time)
  
  (define-syntax (define-constant stx)
    (syntax-case stx ()
      ((_ name datum)
       (begin
         (let ([constant-name  (syntax-object->datum #'name)]
               [constant-value (syntax-object->datum #'datum)])
           ;; Register the constant name and value for this compilation,
           ;; but first check that the constant hasn't been defined before
           (if (constant-registered? constant-name)
               (syntax-error "define-constant: can't redefine constant: " (syntax name))
               (register-constant constant-name constant-value)  ; <- compile-time of constants.scm
               )
           
           ;;; Register for future compilations:
           ; If a module with a (register-constant ...) is required at compile time by another
           ; module M, then the constant names needs to be registered at the compile time of M.
           ; The right hand side of a define-syntax is evaluatued at compiletime, so
           ; we (mis)use it to register the constant name at the compile time of M.
           #`(begin-for-syntax 
                 (register-constant 'name #,constant-value)))))))
                                
  (define-syntax (constant stx)
    (syntax-case stx ()
      ((_ name)
       (let* ([constant-name  (syntax-object->datum #'name)]
              [constant-value (lookup-constant 
                               constant-name 
                               (lambda ()
                                 (syntax-error "constant: unknown constant; " (syntax name))))])
         (datum->syntax-object #'stx constant-value)))))
  )


Now we get:

> (module trig mzscheme
  (require constants)
  (define-constant pi    (* 4 (atan 1)))
  (define-constant pi/2  (* 2 (atan 1))))

> (require constants)
> (require trig)

> (constant pi/2) 
1.5707963267948966


 




More information about the plt-scheme mailing list