ChezScheme icon indicating copy to clipboard operation
ChezScheme copied to clipboard

`eval-when` doesn't work under `module` or `library`

Open rvs314 opened this issue 1 year ago • 3 comments

The eval-when page in CSUG gives an example of how to use eval-when in order to make a definition available in multiple phases:

(eval-when (compile load eval)
  (define nodups?
    (lambda (ids)
      (define bound-id-member?
        (lambda (id ids)
          (and (not (null? ids))
               (or (bound-identifier=? id (car ids))
                   (bound-id-member? id (cdr ids))))))
      (or (null? ids)
          (and (not (bound-id-member? (car ids) (cdr ids)))
               (nodups? (cdr ids)))))))

(define-syntax mvlet
  (lambda (x)
    (syntax-case x ()
      [(_ ((x ...) expr) b1 b2 ...)
       (and (andmap identifier? #'(x ...))
            (nodups? #'(x ...)))
       #'(call-with-values
           (lambda () expr)
           (lambda (x ...) b1 b2 ...))])))

(mvlet ((a b c) (values 1 2 3))
  (list (* a a) (* b b) (* c c)))

This works when put directly into a file, but doesn't work when inside of a library or module form:

(module test ()
  (eval-when (compile load eval)
    (define nodups?
      (lambda (ids)
        (define bound-id-member?
          (lambda (id ids)
            (and (not (null? ids))
                 (or (bound-identifier=? id (car ids))
                     (bound-id-member? id (cdr ids))))))
        (or (null? ids)
            (and (not (bound-id-member? (car ids) (cdr ids)))
                 (nodups? (cdr ids)))))))
  
  (define-syntax mvlet
    (lambda (x)
      (syntax-case x ()
        [(_ ((x ...) expr) b1 b2 ...)
         (and (andmap identifier? #'(x ...))
              (nodups? #'(x ...)))
         #'(call-with-values
               (lambda () expr)
             (lambda (x ...) b1 b2 ...))])))
  
  (mvlet ((a b c) (values 1 2 3))
         (list (* a a) (* b b) (* c c))))

Loading this causes the following error:

Exception: attempt to reference out-of-phase identifier nodups? at line 19, char 16 of /path/to/test.scm

The code does work if the definition of nodups? is wrapped in a meta keyword, but this doesn't let you use it at runtime. Is there a way to get around this? Is this the expected behavior? If so, can it be documented? If not, what is the issue? I'd be happy to help fix it if need be.

rvs314 avatar Oct 12 '24 22:10 rvs314

Putting these definitions inside a library or module form makes them no longer top-level forms, which makes eval-when kinda useless.

The treatment of local expressions or definitions (those not at top level) that are wrapped in an eval-when depends only upon whether the situation eval is present in the list of situations. If the situation eval is present, the definitions and expressions are evaluated as if they were not wrapped in an eval-when form, i.e., the eval-when form is treated as a begin form. If the situation eval is not present, the forms are ignored; in a definition context, the eval-when form is treated as an empty begin, and in an expression context, the eval-when form is treated as a constant with an unspecified value.

The simplest way that I know of to provide some utility functions available at both expand and run time is to put them in their own library, separate from the one that uses them in syntax transformers.

jltaylor-us avatar Oct 13 '24 20:10 jltaylor-us

Putting these definitions inside a library or module form makes them no longer top-level forms, which makes eval-when kinda useless.

This seems pretty counter-intuitive, as TSPL describes top-level programs as essentially the same thing as library forms:

A top-level program is not a syntactic form per se but rather a set of forms that are usually delimited only by file boundaries. Top-level programs can be thought of as library forms without the library wrapper, library name, and export form. The other difference is that definitions and expressions can be intermixed within the body of a top-level program but not within the body of a library. Thus the syntax of a top-level program is, simply, an import form followed by a sequence of definitions and expressions.

The simplest way that I know of to provide some utility functions available at both expand and run time is to put them in their own library, separate from the one that uses them in syntax transformers.

This solves the issue, but seems to limit optimization opportunities.

rvs314 avatar Oct 14 '24 19:10 rvs314

This seems pretty counter-intuitive, as TSPL describes top-level programs as essentially the same thing as library forms:

Things inside a library form are also not top-level.

jltaylor-us avatar Oct 15 '24 00:10 jltaylor-us