Summary
Four let binding variants currently work in WS-mode .prologos files, but only some are tested and none are documented in prologos-syntax.md. This issue tracks a small docs+tests pass to make the supported surface explicit, without changing parser/macro behavior.
Surfaced during review of PR #11 (pitfall #5: multi-line let body) — the expand-let family supports more variants than expected, all functional, none surfaced to users.
The four working variants
Verified empirically (each evaluates [int+ x y] correctly in a let-bound context):
| Form |
Example |
Macro path |
| Flat-pair, untyped |
let [x 5 y 6] body |
expand-let-bracket-bindings flat-pair branch (macros.rkt:4358) |
:=-marked, untyped |
let [x := 5 y := 6] body |
expand-let-bracket-bindings := branch via parse-assign-bindings |
:=-marked, typed |
let [x : Int := 5 y : Int := 6] body |
Same path; : consumed before := |
| Sequential per-line (block-style) |
let x := 5\nlet y := 6\nbody |
Each line is one expand-let invocation; scopes stack |
What does NOT work:
- Typed without
:=: let [x : Int 5 y : Int 6] body — fails with Unbound variable :. Structural reason: types can span multiple tokens (e.g., x : Map Keyword Int), and without := the parser cannot disambiguate where the type ends and the value begins.
Why pluripotential is the right state for now
Three observations from the review discussion:
- The flat-pair /
:= choice maps to a real ergonomic difference. Quick local bindings (counters, intermediate results) read fine flat-pair. Bindings with explicit types or complex RHS expressions read markedly better with :=. Two forms, two cognitive loads — not redundant.
- Cross-language intuitions split here. Clojure → flat-pair. Haskell/ML →
let x = 5 in body (:= analog). Lisp → nested. Picking one form prematurely picks a winner against half our potential contributors' intuitions.
- Cost of supporting multiple variants is documentation, not code. All four implementations exist and are tested at the macro level. The cost is documenting them; the benefit is gathering usage data before codifying.
Scope
This is docs + tests only — no compiler/parser change.
Out of scope (intentional)
- Don't yet pick a canonical "one true form". The pluripotential discipline says: track usage over the next few months. If one form dominates in examples and contributor PRs, that's the natural canonical. If usage stays mixed, that's a real signal that pluripotential is a feature.
- Don't deprecate any variant. All four work; collapsing now would make the scope larger and force a decision we don't yet have data for.
- Don't refactor the macro layer.
expand-let has 5 sexp branches; that's a separate cleanup track if we ever decide IR canonicalization is worth doing.
Source
Priority
Low. Working surface; gap is purely about discoverability. Pick up when next doing a stdlib syntax/style pass.
Summary
Four
letbinding variants currently work in WS-mode.prologosfiles, but only some are tested and none are documented inprologos-syntax.md. This issue tracks a small docs+tests pass to make the supported surface explicit, without changing parser/macro behavior.Surfaced during review of PR #11 (pitfall #5: multi-line
letbody) — theexpand-letfamily supports more variants than expected, all functional, none surfaced to users.The four working variants
Verified empirically (each evaluates
[int+ x y]correctly in alet-bound context):let [x 5 y 6] bodyexpand-let-bracket-bindingsflat-pair branch (macros.rkt:4358):=-marked, untypedlet [x := 5 y := 6] bodyexpand-let-bracket-bindings:=branch viaparse-assign-bindings:=-marked, typedlet [x : Int := 5 y : Int := 6] body:consumed before:=let x := 5\nlet y := 6\nbodyexpand-letinvocation; scopes stackWhat does NOT work:
:=:let [x : Int 5 y : Int 6] body— fails withUnbound variable :. Structural reason: types can span multiple tokens (e.g.,x : Map Keyword Int), and without:=the parser cannot disambiguate where the type ends and the value begins.Why pluripotential is the right state for now
Three observations from the review discussion:
:=choice maps to a real ergonomic difference. Quick local bindings (counters, intermediate results) read fine flat-pair. Bindings with explicit types or complex RHS expressions read markedly better with:=. Two forms, two cognitive loads — not redundant.let x = 5 in body(:=analog). Lisp → nested. Picking one form prematurely picks a winner against half our potential contributors' intuitions.Scope
This is docs + tests only — no compiler/parser change.
prologos-syntax.md§ Definitions / let with the four variants, examples, and the typed-without-:=failure mode (with the structural explanation)docs/spec/grammar.organddocs/spec/grammar.ebnfif the formal grammar there needs to reflect the variantstests/test-eq-let-surface-01.rkt,tests/test-let-arrow-syntax.rkt,tests/test-let-multiline-ws.rktfor gaps)Out of scope (intentional)
expand-lethas 5 sexp branches; that's a separate cleanup track if we ever decide IR canonicalization is worth doing.Source
racket/prologos/macros.rkt:4278-4370(expand-let,expand-let-bracket-bindings)tests/test-eq-let-surface-01.rkt(sexp + some WS),tests/test-let-arrow-syntax.rkt,tests/test-let-multiline-ws.rkt(added by Multi-line let body wrapping in expand-let (eigentrust pitfall #5) #11)Priority
Low. Working surface; gap is purely about discoverability. Pick up when next doing a stdlib syntax/style pass.