Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eval-when doesn't work under module or library #878

Open
rvs314 opened this issue Oct 12, 2024 · 3 comments
Open

eval-when doesn't work under module or library #878

rvs314 opened this issue Oct 12, 2024 · 3 comments

Comments

@rvs314
Copy link

rvs314 commented Oct 12, 2024

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.

@jltaylor-us
Copy link
Contributor

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.

@rvs314
Copy link
Author

rvs314 commented Oct 14, 2024

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.

@jltaylor-us
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants