-
Notifications
You must be signed in to change notification settings - Fork 141
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
Syntactic closures behave unexpectedly (different from MIT scheme) #992
Comments
Thanks for the report! This is something of a known issue, though I don't have time to work on it now. Yes, I think a faithful syntactic closure implementation can deal with unhygienic macros just fine. |
MIT Scheme's syntactic closure implementation (which I think can be called faithful) cannot implement correct unhygienic macros that work together with hygienic |
I believe that MIT scheme'a syntactic closure implementation is 'faithful', but I suspect that its syntax-rules transformer implementation is bugged in how it interacts with syntactic closures. I don't see why syntax-rules can't be made to work fine on top of syntactic closures so that the example works. After all, the implementation should theoretically be able to implement the 'wrapper' example with syntax-rules by rewriting it to the equivalent macro purely 'inside the world of syntactic closures', which works. |
A |
Sorry, I don't follow your argument that a Regarding |
I assume MIT scheme also expands syntax-rules into an er transformer, as chibi does. This indeed doesn't work, as it wouldn't work to use an er transformer to define the wrapper macro above, though using sc-transformer does. The problem. er transformers are strictly less powerful, as syntactic closures have a free-names parameter, and you can also get fine grained environment capture with capture-syntactic-environment. As alexshinn points out, sc-macro-transformers macros are passed in the environment of the usage, though it implicitly wraps the form returned in the environment of the macro keyword. A working syntax-rules macro in terms of syntactic closures would wrap the entire expanded template in the macro keyword's environment. The sub parts of the template filled by the actual input form via the pattern matching variables are wrapped in a syntactic closure with the passed in environment. This causes any unhygienic macro use in the syntax rule template to expand in the isolated environment of the macro keyword, so that the unhygienic binding introduced don't leak to the usage environment or vice versa, that any names in the usage environment (like exit in the above example) do not identify with any unhygienic names introduced by an unhygienic macro in the template. Notice how er-macro-transformer doesn't let you do this type of isolation, since it can only rename and it doesn't have a free-names parameter to offer fine grained control over how raw symbol names are processed. Chibi should switch to this way of defining syntax-rules so that the example in the top-level issue post works. I will try to get a POC implementation of syntax-rules with raw syntactic closures working as soon as I understand how the ellipses processing works. |
There are unhygienic macros (where the non-hygiene comes from constructing names) where knowledge of the environment of the usage is not enough and where the macro needs to be able to wrap a constructed identifier in the syntactic environment of an input identifier that is not the macro keyword.
(define-syntax predicate
(lambda (stx)
(syntax-case stx ()
((_ name) (identifier? #'name)
(datum->syntax #'name (string->symbol (string-append (symbol->string (syntax->datum #'name)) "?"))))))) For example
Consider a Thus, the (define-syntax wrapper
(syntax-rules ()
((_) (loop (exit 3))))) would have to be something like (call-with-current-continuation
(lambda (exit)
(let f ()
(make-syntactic-closure env '(exit) `(,(close-syntax 'exit menv) 3))
(f)))) The problem here, however, is that the meaning of |
What I meant by "dumb" is that a
I hope the rest was clarified by my previous post. |
I believe what you're saying is that the |
Of course, it could, but that wouldn't be of any help in the |
Yes, this is true. To make this work you need to provide a function that gets the environment of an input identifier.
No, this is not how By the way, I've implemented this strategy with some debugging code enabled in this gist: https://gist.github.com/karlosz/c0af67d5ad6e4e975a1fc3e60b60a0d0. You can load this gist into mit scheme (it should work in any scheme with 'faithful' syntactic closures with srfi-1 and pretty-print defined), and it will redefine |
In the following discussion, I write (define-syntax foo
(syntax-rules ()
((_ a) (bar (loop (exit a)))))) and a macro invocation of the form
I think we agree that your initial post in this thread shows that this does not play well with unhygienic SC macros, so we are looking for a better solution. If I understand you correctly, you propose that the expansion should look like
Assume that (define-syntax bar
(syntax-rules ()
((_ a) (a)))) Let
This is equivalent to:
We see that we end up with an expansion of the form we had to rule out earlier. The identifier |
Agree.
Shouldn't this be (in your notation):
? Why have you distributed the environment t to the individual identifiers? That isn't how syntactic closures would work; the forms are closed over and the evaluator evaluates the form in the closed over environment wholesale; there is no preprocess phase during macro expansion where such a distributive operation would be done. Under this interpretation, (define-syntax loop
(sc-macro-transformer
(lambda (exp env)
(let ((body (cdr exp)))
`(call-with-current-continuation
(lambda (exit)
(let f ()
,@(map (lambda (exp)
(make-syntactic-closure env '(exit)
exp))
body)
(f))))))))
(define-syntax bar
(syntax-rules ()
((_ a) (a))))
(define-syntax foo
(syntax-rules ()
((_ a) (bar (loop (exit a))))))
(foo (lambda () 1)) I don't get any undefined variable error here (presumably it would have been with |
When (define-syntax bar
(syntax-rules ()
((_ (a (b c)) (a (b c)))
((_ a) a))) I concluded that the implementation of
NB: I had a typo in my code; there is one pair of parentheses too much in my original definition of (define-syntax bar
(syntax-rules ()
((_ a) a))) Then, the extra wrapping in So, check this please: (define-syntax loop
(sc-macro-transformer
(lambda (exp env)
(let ((body (cdr exp)))
`(call-with-current-continuation
(lambda (exit)
(let f ()
,@(map (lambda (exp)
(make-syntactic-closure env '(exit)
exp))
body)
(f))))))))
(define-syntax bar
(syntax-rules ()
((_ (a (b c))) (a (b c)))
((_ a) a)))
;; Redefine global exit so that the Scheme process won't be terminated.
(define (exit ?) (display "EXIT\n"))
(define-syntax foo
(syntax-rules ()
((_ a) (bar (loop (exit a))))))
(foo 1) |
PS For the record and to have something to experiment with, we are aiming for a solution that works as flawlessly as the following syntax-case macro: (define-syntax loop
(lambda (stx)
(syntax-case stx ()
((k e ...)
(with-implicit (k exit)
#'(call-with-current-continuation
(lambda (exit)
(let f ()
e ...
(f))))))))) You can readily load this and the rest of the last test code into Chez, and it will work. For clarity, I used Chez's -- [1] https://github.com/cisco/ChezScheme/blob/3d1579e6c67e145895e6a0e7d0f9bf2b8853fbb3/s/syntax.ss#L7595 |
Another, different problem with your implementation is that your (define-syntax foo
(syntax-rules ()
((_ (f a)) (f a))))
(define-syntax bar
(syntax-rules ()
((_ (x y)) x)))
(let ((x 1) (y 2))
(foo (bar (x y)))) If you want to fix the I don't have a formal proof for it, but I bet that you will end up with a system isomorphic to |
I should add one more comment concerning syntactic closures: Syntactic closures are forms and close over forms. This is explained in detail in [1]. Arbitrary syntax objects aren't forms. A transformer that creates a syntactic closure over a syntax object that does not end up as a form is violating the specification. Thus, any implementation of Syntactic closures and For example, the R5RS macro (define-syntax fast+
(syntax-rules ()
((fast+ 0 y) y)
((fast+ x y) (+ x y)))) is incompatible with syntactic closures, hygienic or not, and cannot be written as an SC macro (playing well with syntactic closures). An obvious (but failing) attempt to write (define-syntax fast+
(sc-macro-transformer
(lambda (exp env)
(if (eqv? (cadr exp) 0)
(close-syntax (caddr exp) env)
`(+ ,(close-syntax (cadr exp) env) ,(close-syntax (caddr exp) env)))))) The problem here is the test We see that in the syntactic closure system, a macro is not allowed to destructure forms. It may only destructure the clauses of the form it represents, e.g. the This problem has not shown up in Chibi because SC macros are basically not used outside the definition of By the above, (define-syntax zero
(syntax-rules ()
((zero) 0))) the macro -- [1] https://www.gnu.org/software/mit-scheme/documentation/stable/mit-scheme-ref/Syntax-Terminology.html |
I think you are right here; by fixing the issues you point out with destructuring above, we need to descend into syntactic closures. In doing so, syntactic closures would need to be wrapped and unwrapped before doing any list operation (such as The isomorphism would then be pretty clear, I think. |
Sorry, you guys seem to be having fun but I'm afraid I'm too busy join in just now 😅 |
The problem would be that the resulting macros would be incompatible with SC and ER macros that do not expect wrapped subforms. Moreover, we have seen above that unwrapping is incompatible with the FREE argument to SC's
In the |
I agree. A Van Tonder-style implementation would retain the properties of explicit renaming which Alex values (at least according to my understanding). It would even allow explicit renaming to remain the mechanism of choice for the macros in Chibi’s standard library, with |
Here's an example where
sc-macro-transformer
behaves differently than on MIT scheme. The MIT scheme version behaves more in line with what I expect.On MIT Scheme, this code displays "Hello, World!" and returns 3, as I would expect. On Chibi, this simply returns "Hello, World!". The behavior on MIT Scheme seems more intuitive, since
loop
only introducesexit
in the environment of the macro definition ofwrapper
, so theexit
identifier bound bylet
should not be captured by the free reference toexit
inloop
, which should be resolved in the environment of thewrapper
macro definition.Futhermore, on both implementations, defining
wrapper
withsyntax-rules
like socauses both implementations to return the 'wrong' result. I think this is a problem with how
syntax-rules
is implemented. It should behave exactly the same as how thesc-macro-transformer
definition ofwrapper
behaves in MIT Scheme. I believe this is what led Marc Nieper-Wißkirchen to claim here https://groups.google.com/g/comp.lang.scheme/c/2gFSbX-Wcy4 that syntactic closures as is can't be used to define unhygienic macros reliably. On the contrary, I believe these are implementation bugs with how thefree-names
parameter of syntactic closures are handled in Chibi, and howsyntax-rules
interacts with syntactic environments (the definition should convert the pattern in an isolated environment as MIT scheme does withsc-macro-transformer
), and that theoretically syntactic closures can deal with these cases fine.The text was updated successfully, but these errors were encountered: