Outer Expression Context

Written by Dominik Pantůček on 2026-04-09

racket

Sometimes it is necessary to use syntax context of macro use site. For example when explicitly relying on the outer expression context for honoring modified application semantics.


This post is going to be rather short, however still very interesting. When writing Racket syntax macro, it may be useful to use #%app from the surrounding context of the macro use site. Yet if the whole expression other than the lexical context should be retained, it may be a bit tricky to get it right.

Consider the following code:

(define-syntax (debug-print stx)
  (syntax-parse stx
    ((_ (op arg ...))
     #'(let ((tmp (op arg ...)))
         (displayln '(args: arg ...))
         tmp))))
(debug-print (+ 1 2))

It prints the usual, expected output:

(args: 1 2)
3

Now what if we want to debug modified procedure application like in the following example?

(let-syntax ((#%app (syntax-rules () ((_ . rest) (list . rest)))))
  (debug-print (+ 1 2)))

The result of the naive macro implementation is definitely not correct:

(args: 1 2)
3

To see why it is not correct, we can evaluate the problematic expression alone:

(let-syntax ((#%app (syntax-rules () ((_ . rest) (list . rest)))))
  (+ 1 2))

And of course, we get:

'(#<procedure:+> 1 2)

The fix is rather simple, however it has to be applied properly. Thankfully, in our simple example, it is not that hard:

(define-syntax (debug-print stx)
  (syntax-parse stx
    ((_ (op arg ...))
     #:with expr (datum->syntax stx (syntax-e #'(op arg ...)) stx)
     #'(let ((tmp expr))
         (displayln '(args: arg ...))
         tmp))))

By stripping the template syntax context and re-introducing the outer expression context in its place, we ensure even the #%app lookup will be performed in the use site context. Inspecting the contents of both original and altered syntax does not reveal any differences as they truly do differ only in their set of scopes. And we finally get the correct result:

(args: 1 2)
'(#<procedure:+> 1 2)

Yes, this was a short one - as advertised - however if you need modified application behavior, it can save you from debugging your code for a very long time.

Hope you liked it as always and see ya next time!