larcenists / larceny

Larceny Scheme implementation
Other
202 stars 32 forks source link

In R7RS programs, right hand sides of definitions are expanded too late #825

Open mnieper opened 5 years ago

mnieper commented 5 years ago

Consider the following program:

(import (scheme base)
        (scheme write))

(define-syntax foo (syntax-rules () ((foo) 1)))
(define x (foo))
(define-syntax foo (syntax-rules () ((foo) 2)))
(display x)
(newline)

When Larceny runs this program, it outputs 2. This violates section 5.4 of the R7RS, which says:

If the define-syntax occurs at the outermost level, then the global syntactic environment is extended by binding the keyword to the specified transformer, but previous expansions of any global binding for keyword remain unchanged.

This means that the first binding of the keyword foo must be used where x is defined. Larceny, however, seems to defer the expansion of the right hand sides until all definitions have been processed.

The R7RS seems to be fundamentally incompatible to the R6RS here. In R6RS, the expansion of the right hand sides have to be deferred because the following is a valid R6RS program.

(import (rnrs base)
        (rnrs io simple))

(define x (foo))
(define-syntax foo (syntax-rules () ((foo) 2)))
(display x)
(newline)

It is unclear whether this deviation to the R6RS was a deliberate choice by the R7RS authors or accidental.

Expanding top-level programs with the R7RS semantics may need two passes over the definitions (where R6RS just needs one).

WillClinger commented 5 years ago

Ouch.

Two passes would be problematic for programs that use syntax-case or other procedural macros. This incompatibility with R7RS may not be important enough to justify breaking R6RS code.

mnieper commented 5 years ago

Caching may be possible so that macros are not expanded twice.

The R6RS semantics seem to be clearer (and easier to implement), so one can hope that a future addendum to the R7RS restores compatibility with the R6RS.