-
-
Notifications
You must be signed in to change notification settings - Fork 648
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
Racket 7 changes the behavior of syntax-parameterize when combined with local-expand #2035
Comments
I believe the problem was actually present for all versions of Racket 7's syntax-parameterize implementation, rather than being specific to the change I made in racket/racket7@6c7574f. Previous versions also local-expanded the body, but handled the parameter environment differently. This seems like a general problem for uses of Perhaps we need a variant of local-expand, or at least local-expand/expression, that respects stop lists established by parent expansions. |
I think you’re right. I was looking at the code in more detail last night after I opened this bug report, and I suspected that might be the case (given how similar the Racket 7 implementation of
I don’t think this alone would fix the problem if syntax parameters are implemented using phase 1 parameters, since that approach only works if the body is fully expanded (since otherwise the syntax parameter will reset to its old value once the expander returns to the pieces of syntax that were left unexpanded due to the stop list). I’m unconvinced that this problem can be fixed as long as syntax parameters are implemented using phase 1 dynamic binding rather than modifying the phase 0 lexical environment (which, if I understand correctly, they used to do prior to that commit). |
The previous Racket 7 implementation also used dynamic binding, just in the expander's context object rather than in a parameter in user code. Racket 6 didn't have
Generally this scenario doesn't make sense to me because we haven't solved the problem of local-expanding under Can you explain what you're trying to achieve with the code that does this? |
Yes, once again you’re right and I was wrong here. I was looking at the history of In any case, I think you’re right that the heart of the issue is a desire to get rid of
I can, yes, and admittedly, your proposed solution of “a variant of local-expand […] that respects stop lists established by parent expansions” would fix my particular use case (but it wouldn’t necessarily fix things in general). For Hackett, I am defining a custom language with different core forms from Racket’s core forms. As we discussed at RacketCon, I wish to do this so that Hackett can have different compilation targets, as well as a representation more amenable to optimization, but in this case, I’m actually not doing that yet. I’m using this technique for Hackett’s type language. Here’s an excerpt from what the real code actually looks like: ;; ---------------------------------------------------------------------------------------------------
;; fully-expanded types
(define-syntaxes [#%type:con #%type:app #%type:forall #%type:qual
#%type:bound-var #%type:wobbly-var #%type:rigid-var]
(let ([type-literal (λ (stx) (raise-syntax-error #f "cannot be used as an expression" stx))])
(values type-literal type-literal type-literal type-literal
type-literal type-literal type-literal)))
(begin-for-syntax
(define type-literal-ids
(list #'#%type:con #'#%type:app #'#%type:forall #'#%type:qual
#'#%type:bound-var #'#%type:wobbly-var #'#%type:rigid-var))
(define-literal-set type-literals
[#%type:con #%type:app #%type:forall #%type:qual
#%type:bound-var #%type:wobbly-var #%type:rigid-var]))
;; ---------------------------------------------------------------------------------------------------
;; type expansion
(begin-for-syntax
(define-syntax-class (type [intdef-ctx #f])
#:description "type"
#:attributes [expansion]
[pattern _
#:do [(println this-syntax)]
#:with {~var || (expanded-type intdef-ctx)}
(local-expand this-syntax 'expression type-literal-ids intdef-ctx)])
(define-syntax-class (expanded-type intdef-ctx)
#:description #f
#:attributes [expansion]
#:commit
#:literal-sets [kernel-literals type-literals]
[pattern (head:#%expression ~! {~var a (type intdef-ctx)})
#:attr expansion (syntax-track-origin #'a.expansion this-syntax #'head)]
[pattern (letrec-syntaxes+values ~! ([(id:id ...) e:expr] ...) () t:expr)
#:do [(define intdef-ctx* (syntax-local-make-definition-context intdef-ctx))
(for ([ids (in-list (attribute id))]
[e (in-list (attribute e))])
(syntax-local-bind-syntaxes ids e intdef-ctx*))]
#:with {~var || (type intdef-ctx*)} #'t]
[pattern (#%type:con ~! _:id)
#:attr expansion this-syntax]
[pattern (head:#%type:app ~! {~var a (type intdef-ctx)} {~var b (type intdef-ctx)})
#:attr expansion (syntax/loc/props this-syntax
(head a.expansion b.expansion))]
[pattern (head:#%type:forall ~! x:id {~var t (type intdef-ctx)})
#:attr expansion (syntax/loc/props this-syntax
(head x t.expansion))]
[pattern (head:#%type:qual ~! {~var a (type intdef-ctx)} {~var b (type intdef-ctx)})
#:attr expansion (syntax/loc/props this-syntax
(head a.expansion b.expansion))]
[pattern (#%type:bound-var ~! _:id)
#:attr expansion this-syntax]
[pattern (#%type:wobbly-var ~! _:id)
#:attr expansion this-syntax]
[pattern (#%type:rigid-var ~! _:id)
#:attr expansion this-syntax])) Essentially, this code uses the Racket macroexpander to convert syntax objects like The expansion uses a stop list because it needs to treat things like Since the expansion is still fundamentally recursive, it’s possible to handle |
I’ve bumped into this again just now, though in a slightly different way this time. In this case, the only problem is that It seems clear that I am abusing the stop list. It is designed to be used for head expansion, but I am using it for a different purpose entirely (effectively creating multiple passes of macroexpansion for the purpose of dictionary elaboration). That said, whether it’s an abuse or not, it’s very useful, so I’d appreciate trying to figure out a way to make this work. First, some context. In Hackett, I need to delay the process of dictionary elaboration, since dictionary elaboration requires fully-solved type information, and type information is only solved by expanding the program. To allow for this, dictionaries are inserted using a However, One thing I realized over the past few days is this: the current handling of the stop list is already broken. For a while, I questioned whether or not the behavior that causes forms to be automatically added to a non-empty stop list served any purpose; if not, I figured we should just remove it (in a backwards-compatible way, of course). But then I realized that partial expansion of However, I’ve recently realized this argument is flawed. While it’s true that partial expansion can mix poorly with local syntax binding forms, any form can be a local syntax binding form, due to the existence of first-class definition contexts. Users of partial expansion already must take care not to lose bindings this way, and currently, we don’t have any mechanisms in place to prevent such things. So the guarantees we’re providing here are weak at best, which makes me a little uncomfortable. It’s tempting to add a |
A question from the naive here: why isn’t the stop list a parameter?
|
The point of |
I understand that people now use local-expand to implement lightweight expanders. The question is how to deal with composition of non-Racket language A with non-Racket language B in such circumstances.
|
How is partial expansion with a stop list getting far enough into the expansion of Or are you trying to replicate Racket's core form expansion and recurring into subexpressions, as you were for the type language and syntax parameters there? Your point about the general issue of lost local syntax bindings reveals an implicit protocol between macros for the main language of Racket expressions and definitions which I had not previously noticed. Generally it is safe to expect an extension of the expander environment or a phase 1 parameterize to persist throughout subexpression expansions for a macro expanded with We probably shouldn't expect it to be safe to use local-expand with Thus, while we could solve the particular issue of Can Hackett work as turnstile does now, where all expansion of type-erased forms, not just dictionary elaboration, is delayed by wrapping with something in the stop list? Turnstile's |
A status update: I have, on a branch, adjusted the expander to finer control of the This weakening I have done of both The new system is also very slow. Hackett compilation times are already very poor, in some part due to recursive uses of Anyway, this is clearly an issue bigger than
I’m actually not familiar with this technique. My guess is that no, it would not actually solve my problem, but I’m curious what exactly Turnstile is doing here and why it’s doing it. Could you explain in a little more detail and/or point me to the relevant place in the Turnstile code? |
Whenever a type rule has computed the type of a term and is ready to return the erased term and the type, it does so by expanding to syntax of the form The result is that we build up the entire type-erased term during typechecking before it gets expanded at all, and when it gets expanded it is expanded all together. So there's no problem if a typed term erases to something using Here's where we add the wrapper: https://github.com/stchang/macrotypes/blob/7ad0789111a83b97d02f5749f749396d72c4fb1b/macrotypes/typecheck-core.rkt#L349 |
Closing this because the discussion has long since settled, and the current implementation is here to stay (which I think is just fine). |
Flamebait alternate title: Racket 7 breaks my program!
Given the below program:
…running this on Racket 6.12 and HEAD produces different results:
This is because
syntax-parameterize
in Racket 7 recursively expands its body, which causes problems when it’s important that certain forms aren’t expanded. This change was introduced in racket/racket7@6c7574f.Pinging @mflatt and @michaelballantyne as the relevant parties.
The text was updated successfully, but these errors were encountered: