
@lexi.lambda I don’t know if that last example can work at all. The local-expand
form can’t generally deal with a mixture of top-level or module-level evaluation and expansion. For example, I struggle with the question: What would it mean to define val
when the definition can be discarded after local-expand
?

So, your comment about delaying expansion seems like potentially the right idea, but I’m unclear on what part can be delayed. You might need to expand with a non-empty stop list and recognize sequences the way forms with internal-definition positions often do.

@mflatt That makes sense. This is probably an XY problem. I want to expand forms entered into the REPL to see if they end in a definition or expression, so I can print the expression’s type as well as its result. However, I’m not completely sure what the right way to do that is with partial expansion.

@lexi.lambda the TR repl does that

I thought TR might have some prior art. What is its technique?


@samth I’m not entirely sure what I’m looking at

You probably have to look at the functions that calls

@samth does it basically just recursively call local-expand
with kernel forms as the stop list?

I’m following the code as best as I can but TR is big

@samth @mflatt Unless I’m overlooking something, given what you said, I think doing partial expansion might still expand too much, since it would still require expanding the form that needs that define-for-syntax
binding.

I don’t feel like I understand how expansion at the top level works, though.

Hmm… even after trying to simplify my macro to play nicer with the top level, I’ve discovered even something as simple as this still doesn’t work: #lang racket
(require (for-syntax racket/syntax
syntax/transformer)
syntax/parse/define)
(define-simple-macro (m1 id:id)
(begin
(define-syntax id (make-variable-like-transformer #'"something"))
(m2 id)))
(define-simple-macro (m2 x)
#:with result (local-expand #'x 'expression '())
'result)
(begin-for-syntax
(println (local-expand #'(m1 foo) 'top-level '())))

Partial expansion wouldn’t help here, because in order to determine if (m1 foo)
is an expression, we’d need to expand (m2 id)
, which fails.

@lexi.lambda I can’t offer a long answer right now, but have you seen expand-syntax-top-level-with-compile-time-evals
from syntax/toplevel
?

No, I hadn’t! I wasn’t aware of syntax/toplevel
; that looks helpful.

@mflatt syntax/toplevel
looks very close to what I’m looking for, but I can’t figure out how to use it correctly—if I use it at the top level or in the runtime body of a module, it does what I’d expect, but if I call it at phase 1 or during expansion of #%top-interaction
, (expand-syntax-top-level-with-compile-time-evals #'(m1 foo))
produces (#%app m1 foo)
for some reason. It seems like maybe that happens because it treats the syntax object as a phase 1 expression? But I need to be able to expand it as a runtime expression.

But now I need to drive for an hour, so I’ll be away from the keyboard as well.

…hours later, my understanding is that I’d need access to the REPL’s namespace in order to properly use expand-syntax-top-level-with-compile-time-evals
, but it doesn’t seem like current-namespace
is set to the right thing during the expansion of #%top-interaction
, and I’m not sure if there’s any way to get ahold of it.

I suppose I can do something like this: (define-simple-macro (#%top-interaction . form)
(#%top-interaction*
(expand-syntax-top-level-with-compile-time-evals/flatten
(quote-syntax form))))
(define (#%top-interaction* forms)
(for/last ([form (in-list forms)])
(syntax-parse form
#:literal-sets [kernel-literals]
[(define-syntaxes . _) (void)]
[_ (eval-syntax form)])))
…but I’m not sure if that’s a recommended approach or not!