# Alternatives to scope sets for hygiene?

**URL:** <https://racket.discourse.group/t/alternatives-to-scope-sets-for-hygiene/4377>\
**Category:** General\
**Tags:** macro\
**Created:** [September 7, 2026, 9:15pm UTC](https://racket.discourse.group/t/alternatives-to-scope-sets-for-hygiene/4377 "2026-09-07T21:15:01Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![jgarvin](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/jgarvin/32/2011_2.png) [@jgarvin](https://racket.discourse.group/u/jgarvin)\
**Post date:** [September 7, 2026, 9:15pm UTC](https://racket.discourse.group/t/alternatives-to-scope-sets-for-hygiene/4377/1 "2026-09-07T21:15:01Z")

</div>

My understanding is that Racket hygiene being based on scope sets was chosen in part for historical reasons; e.g. the scope pruning scheme was added to be backwards compatible with existing idioms.

I've skimmed the docs and the original scope sets paper and I still find the details a bit confusing to work through. I'm wondering if backwards compatibility weren't a concern, if we could get away with a simpler scheme: when you make a syntax quote, identifiers in it can only resolve to things that were in scope when you constructed the quote OR bindings introduced within the same syntax quote, but _not_ from any other syntax quote. If you wanted to actually refer to the same binding across multiple syntax quotes, you'd have to explicitly splice it into the binding and reference quotes:

```scheme
(define id #'x)

(define definition #`(define #,id 1))
(define reference #`(+ #,id 2))

#`(begin #,definition #,reference)

```

This also means if you wrote:

```scheme
(let ([ref #'x])
  #`(let ([x 1]) #,ref))

```

That the `#'x` spliced in by `#,ref` would _not_ be a reference to the `x` bound in the `let`. You would instead have to write:

```scheme
(let ([ref #'x])
  #`(let ([#,ref 1]) #,ref))

```

For the simple types of macros I've written in Racket before I don't think this would pose a problem, but I don't know if there is advanced macrology where this breaks? 🙂

Another way to think about this is that within a syntax quote, bindings are implicitly gensym'd and any references to them are patched appropriately, and when you just write a standalone `#'identifier` that's doing a gensym with no references, that you can then only produce more references to by splicing into other quotes.

---

<div class="post-metadata">

**Author:** ![ryanc](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/ryanc/32/71_2.png) [@ryanc](https://racket.discourse.group/u/ryanc)\
**Post date:** [September 7, 2026, 9:59pm UTC](https://racket.discourse.group/t/alternatives-to-scope-sets-for-hygiene/4377/2 "2026-09-07T21:59:33Z")

</div>

It sounds like you're proposing to move the "freshness" boundary from the macro expansion step to the syntax template expression. If so, it sounds like Andre van Tonder's hygienic macro proposal, which eventually became [SRFI-72](https://srfi.schemers.org/srfi-72/srfi-72-1.1.html).

I think it's a move in the wrong direction, on the general principle that _impure abstractions_ are usually bad, or at least their costs need to be considered. To explain that, let me first point out an example of a pure abstraction: Racket's `lambda` form. Racket respects the βᵥ equation: `((lambda (x) e) v) = e[v/x]`. That is, there is no observable difference (aside from debugging information) between an expression that contains a value directly and the application of a function that receives the value and refers to it in the right place (assuming no scoping changes). So Racket's `lambda` creates a _pure abstraction_, in a way that, for example, Javascript's `function` does not, since Javascript allows you to retrieve "the current function arguments", `return` is sensitive to function boundaries, variable hoisting, and possibly other reasons. Javascript `function` comes with extra baggage. Racket `lambda` doesn't, and so there are fewer things to worry about when using procedural abstraction in Racket.

Hygienic macros are not pure abstractions with respect to surface syntax. For example:

```
(let ([x 1]) (+ x 2))
≠
(define-syntax-rule (m e) (let ([x 1]) e))
(m (+ x 2))

```

In fact, that's kind of the point of hygiene. Hygiene is usually more useful than strict purity, but if you do sufficiently complicated things with macros, especially if you limit yourself to `syntax-rules`, you will likely find yourself occasionally saying "I wish hygiene didn't apply to _this_ macro". But at least the granularity of impurity for Racket's hygienic macros is the macro expansion step. Changing the granularity to the syntax template instead means that you have additional worries when writing macros whose result is too complicated for a single template, or for refactoring a big syntax template into smaller pieces.
