Problem: if you write a macro that uses ‘define-foo’, then the identifier ‘foo’ is made unique (rewritten to ‘foo-123456’ or so).
(define-syntax-rule (define-foo* value)
(define-foo value))
(define-foo* 42) ;does not define ‘foo’
👎
Discussion
Problem: if you write a macro that uses ‘define-foo’, then the identifier ‘foo’ is made unique (rewritten to ‘foo-123456’ or so).
(define-syntax-rule (define-foo* value)
(define-foo value))
(define-foo* 42) ;does not define ‘foo’
👎
It’s possible to break hygiene with ‘datum->syntax’; below, the macro introduces the top-level identifier ‘foo’, as-is:
(define-syntax define-foo
(lambda (s)
(syntax-case s ()
((_ value)
(with-syntax ((foo (datum->syntax s 'foo)))
#'(define foo value))))))
(define-foo 42)
👍
@civodul This code is not compliant – the first argument to datum->syntax must be an identifier because the context on an identifier is (1) guaranteed to be present (any other type of syntax object might be unwrapped) and (2) unambigous (a compound syntax object of any kind might carry multiple contexts)
@dpk Uh, silly me. 🤦
And it fixes the ‘define-foo’ example.
I have more involved code where macro-introduced top-level identifiers are still being rewritten though. I wonder what I’m missing.
@civodul Usually it’s sufficient to capture the macro use keyword’s context:
(define-syntax define-foo
(lambda (s)
(syntax-case s ()
((k value)
(with-syntax ((foo (datum->syntax #'k 'foo)))
#'(define foo value))))))
but there are exceptions, usually when identifiers are generated by adding bits to other identifiers (like implicit naming in define-record-type)
Problem: if you write a macro that uses ‘define-foo’, then the identifier ‘foo’ is made unique (rewritten to ‘foo-123456’ or so).
(define-syntax-rule (define-foo* value)
(define-foo value))
(define-foo* 42) ;does not define ‘foo’
👎
@civodul This is correct behaviour for reasons explained in the R7RS large draft: https://r7rs.org/large/fascicles/macro/1/macros-and-hygiene.html#hygiene-definition:~:text=Within%20the%20context%20of%20hygienic%20macro%20expansion
There’s no real watertight technical solution, unfortunately; if you use the keyword-capturing trick mentioned in the previous toot, you also have to make sure the underlying macro is in scope with the expected name.
The design pattern solution is that every unhygienic macro should just be syntactic sugar for some other, hygienic macro (as, again, in implicit naming define-record-type)
@dpk My use case here is introducing top-level identifiers from a macro that parses https://codeberg.org/swagger.v1.json and generates bindings.
It’s a situation that requires hygiene to be turned off, but I’m reaching the conclusion that it won’t be possible—that I’ll have to generate a Scheme file rather than do it all at macro-expansion time.
What’s your take?
@civodul Do you have time to jump onto Jitsi/similar and share what you’re working on?
@dpk Thanks for unblocking the situation on IRC earlier today!
Turns out the solution, as you found it, was to pass ‘datum->syntax’ the right context—i.e., a syntax object corresponding to the top level.
Making progress!
This is pretty annoying and I don’t see a good way to work around that.
Thoughts, #Scheme folks?
This is a small personal instance of Bonfire in the Fediverse.