From 7d0e19ee7fa05ae86b88ed9a49186d004d376400 Mon Sep 17 00:00:00 2001 From: Nitjsefnie Date: Sat, 18 Jul 2026 19:30:19 +0000 Subject: [PATCH] docs: state the arity-1 of-callee carve-out (#635) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SPEC.md's "one rule, one sentence" and LANGUAGE_CONTRACT.md's argument- unpacking section both claim brackets after `of` always spread into the callee's parameters positionally. That's false for a 1-parameter, non-defaulted callee: it has only one slot, so a 2+-element literal list re-collects whole and binds to that parameter instead of spreading (`one of [3, 4]` binds `a = [3, 4]`, not `a = 3`, for `define one(a)`). This is exactly what keeps `len of [1, 2]` and `print of [1, 2]` working — the implementation is right and load-bearing, so the docs are corrected to describe it instead. Adds the carve-out next to both "one rule" statements in SPEC.md (the call section and the evaluation-model reference) and alongside the existing `f of []` note in LANGUAGE_CONTRACT.md, plus a new executable SPEC.md example that pins the behavior under the doc-example gate. Co-Authored-By: Claude Sonnet 4.6 --- docs/LANGUAGE_CONTRACT.md | 11 +++++++++++ docs/SPEC.md | 24 +++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/LANGUAGE_CONTRACT.md b/docs/LANGUAGE_CONTRACT.md index ffc4639a..e8bd07ec 100644 --- a/docs/LANGUAGE_CONTRACT.md +++ b/docs/LANGUAGE_CONTRACT.md @@ -209,6 +209,17 @@ callee's parameters in order: `m = 2, v = 3`. - Extra elements are ignored; parameters with no matching element take their default, else `null`. +- **Arity-1 carve-out:** the elements-bind-in-order rule above assumes + a callee with 2+ parameters. A 1-parameter, non-defaulted callee has + only one slot, so a 2+-element list doesn't distribute into it (and + doesn't just bind the first element, discarding the rest per "extra + elements are ignored") — the whole list re-collects and binds to + that one parameter: for `define one(a)`, `one of [3, 4]` binds + `a = [3, 4]`, not `a = 3`. This is what keeps `len of [1, 2]` + returning `2` and `print of [1, 2]` printing the list. The `f of []` + half of this same exception — an empty list still binds `a = []` + rather than firing a zero-arg default — is covered under Default + parameter values below. - **Parentheses always mean one argument** (issue #355). To pass a literal list *whole*, parenthesise it: `f of ([a, b])` binds the list `[a, b]` to the first parameter, and `f of ([7])` binds the one-element list diff --git a/docs/SPEC.md b/docs/SPEC.md index 5dcc9f90..ee1d65e6 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -630,6 +630,24 @@ whole. (Before #405, `f of [x]` bound the whole list `[x]` to the first parameter — lint `W017` flags the historically ambiguous 1-element form and names both unambiguous spellings.) +**Arity-1 carve-out.** "One rule" describes the call site, not what a +1-parameter, non-defaulted callee does with the list it receives. Such +a callee has only one slot, so a 2+-element argument list doesn't +distribute into it — the whole list re-collects and binds to that one +parameter instead: `one of [3, 4]` (with `define one(a)`) binds +`a = [3, 4]`, not `a = 3`. This is what keeps `len of [1, 2]` returning +`2` and `print of [1, 2]` printing the list — removing the carve-out +would break every 1-parameter function that takes a list. + +```eigenscript +define one(a) as: + return a +print of (type of (one of [3, 4])) +``` +```output +list +``` + ```eigenscript define first(a, b) as: return a @@ -1471,7 +1489,11 @@ The facts that govern every program, in one place: arithmetic: `f of x + 1` is `(f of x) + 1`. 3. **Argument spreading**: a *literal* list argument with 2+ elements spreads into parameters; a 1-element literal list does **not**; a - list passed via a variable never spreads. + list passed via a variable never spreads. Exception: a 1-parameter, + non-defaulted callee has only one parameter to spread into, so a + 2+-element list doesn't spread there either — it re-collects whole + and binds to that one parameter (`one of [3, 4]` binds `a = [3, 4]` + for `define one(a)`). 4. **Scope**: `is` updates the nearest enclosing binding or creates a local; `local` forces the current scope. Functions see and may mutate their defining environment (closure capture by reference).