[plt-scheme] create complex object @ compile time but made available @ run time?

From: Jens Axel Søgaard (jensaxel at soegaard.net)
Date: Wed Jun 13 04:42:28 EDT 2007

YC wrote:
> On 6/13/07, *Jens Axel Søgaard* <jensaxel at soegaard.net 
> <mailto:jensaxel at soegaard.net>> wrote:
>      > The problem here is not that it is a complex object, but that
>      > structure definitions are generative - it would have worked
>      > if you used vectors. Back to the structure problem. To quote the
>      > documentation:
>      >   "Each time a define-struct expression is evaluated, a new structure
>      >    type is created with distinct constructor, predicate,
>     accessor, and
>      >    mutator procedures."
> Thanks for the great explanation Jens - this makes great sense - but is 
> there a way to prevent struct being evaluated twice, once for (require) 
> and once for (require-for-syntax)?  

The semantics of require and require-for-syntax demand that the
required module is evaluated twice, so I'd be inclined to say no.
But there might be an alternative route. See below.

> In the example you cited the double 
> eval is clearly intended because each define-struct is enclosed 
> lexcially and it makes sense that they are different structs (like 
> enclosed lambda & variables are different), but IMHO 
> (require-for-syntax) and (require) evaluate the same code twice is 
> _surprising_ (I thought it's more like "importing" names to the 
> transformation environment) - love to hear your and others' take on 
> whether this is expected.

The imported names are bound to values. Somewhere and at some time
these values must have been evaluated. To run a piece of code,
it must be expanded first. Since macros and what they depend on live
in the transformation environment, values and structure definitions
are needed in the transformer environment before they are needed
in the runtime environment. One could argue that the values in the
transformer enviroment could be reused in the runtime environment
(that's what the defmacro system does), but that leads to a number
of problems. Problems that disappear, when the the runtime and the
transformer environment are separated.

  >     One solution is to calculate the values at expansion time, and
>     then expand to (create-foo val1 ...). 
> This is how I currently have it (but my code have multiple nested struct 
> creations, which makes it difficult to fully leverage expansion time) - 
> until I thought - why not try to see if the object(s) can be generated 
> in compile time and return to run time... close but not quite :)

*Maybe* the following technique can be used. The example features
structures in structures. A square consists of four points. The
macro (create-square n) calculates at expansion time the coordinates
of the four corners of a square with side length 2*n (just to
show an actual computation takes place).

(module geom mzscheme
   (provide (all-defined))
   (require (lib "serialize.ss"))
   (define-struct point (x y) (make-inspector))
   (define-struct square (a b c d) (make-inspector)))

(require geom)
(require-for-syntax geom)
(require-for-syntax (lib "pconvert.ss"))

(define-syntax (create-square stx)
   (syntax-case stx ()
     ((_ side)
      (number? (syntax-object->datum #'side))
      (let ([s (* 2 (syntax-object->datum #'side))])
        (let ([a (make-point 0 0)]
              [b (make-point 0 s)]
              [c (make-point s s)]
              [d (make-point s 0)])
          (let ([the-square (make-square a b c d)])
            (with-syntax ((evaluable-sexpr
                           (print-convert the-square)))

 > (create-square 5)
#(struct:square #(struct:point 0 0) #(struct:point 0 10)
                 #(struct:point 10 10) #(struct:point 10 0))

 > (syntax-object->datum
     #'(create-square 5)))
  (#%app make-point (#%datum . 0) (#%datum . 0))
  (#%app make-point (#%datum . 0) (#%datum . 10))
  (#%app make-point (#%datum . 10) (#%datum . 10))
  (#%app make-point (#%datum . 10) (#%datum . 0)))

Jens Axel Søgaard

Posted on the users mailing list.