Skip to content

Assign-once bindings reachable from the macro layer #108

Description

@Technologicat

The gap

assignonce gives assign-once bindings, but the macro layer has no way to produce one — the let constructs build a plain env. So macro-enabled code cannot use the discipline at all, and a user who wants it has to drop out of the macro layer.

env.finalize() is not a substitute. The two guarantees are orthogonal:

  • finalize() freezes the set of names — no additions, no deletions — while existing bindings stay rebindable.
  • assignonce fixes each binding's first value, while the set of names stays open.

Proposed decomposition

Two halves, with quite different cost profiles, and they are separable.

1. Reachability (the actual gap). Let env carry the assign-once discipline, so the let constructs can produce one. This is a runtime change; the macro layer needs no new intelligence, since it already builds the env. Independently useful and low risk.

2. Static rejection (the rackety part). Have the expander raise SyntaxError on an assignment to an already-bound name in an assign-once env, where that is statically detectable — so the macro-layer compiler rejects invalid code rather than deferring to a runtime error.

On the second half

Worth doing only if it pays for its complexity, and it is not obvious that it does.

The easy case is already within reach: letdoutil has isenvassign and UnexpandedEnvAssignView, so detecting two assignments to the same name within one lexical block is a local syntactic check.

What escapes it: assignment inside a loop or conditional, an env passed to a helper function, and any aliasing. Since the check can only ever be partial, runtime enforcement has to stay regardless — so the static half buys earlier detection for the mistakes least likely to survive a first test run, at the price of permanent macro-layer complexity.

There is also a design-philosophy pull. Racket can reject statically because its binding forms are the language; here an env is a runtime object that can be passed around, so the analysis is necessarily incomplete. And doc/design-notes.md says to minimize macro magic — if a feature can be implemented in the pure-Python layer, that is where it belongs.

Suggested order

Do (1), keep runtime enforcement, and revisit (2) once something real is using the feature and we know which mistakes actually occur.

— Claude (Opus 5)

Metadata

Metadata

Assignees

No one assigned

    Labels

    discussionThinking it over - comments welcome!enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions