Skip to content

extern: a unit parameter contributes no argument, wherever it sits (#123) - #125

Merged
simontreanor merged 1 commit into
mainfrom
fix/unit-extern-params
Aug 31, 2026
Merged

extern: a unit parameter contributes no argument, wherever it sits (#123)#125
simontreanor merged 1 commit into
mainfrom
fix/unit-extern-params

Conversation

@simontreanor

Copy link
Copy Markdown
Owner

#123's table showed three shapes with three meanings: a nullary extern called with zero arguments (right), a leading unit splitting the call (unit -> int -> int = abs emitted abs()(-5) — the zero-argument rule fired and the remaining arguments applied to its result), and a mid-list unit travelling as None (max(3, None, 5)). This lands the issue's requested rule: a unit parameter contributes no Python argument, wherever it sits, which subsumes the nullary case instead of special-casing it.

  • extern_unit_slots replaces nullary_externs: the positions of plain unit parameters along the curried list (distinct from thunk slots, which are function-typed unit -> a).
  • A new build_call_units handles every arity: full application emits one call with the units dropped (abs(-5), max(3, 5)); over-application folds extras as everywhere else; partial application closes over a lambda — functools.partial cannot drop a unit that has not arrived, the lambda accepts and ignores it (pick 4lambda _pf_k0, _pf_k1: max(4, _pf_k1)). A dropped argument that could have effects still runs, as its own statement, with neighbours hoisted to keep left-to-right order.
  • Bare references and project-mode bindings get unit_dropping_lambda (the all-unit case keeps today's lambda *_: shape); receiver methods, property folds and receiver_lambda drop units the same way; the unit block now also thunk-wraps, which the old nullary path never did.
  • The parser's ...-slot arithmetic counts only non-unit arguments, so the extern the finding actually wanted — unit -> (unit ->{io} unit) -> Thread = threading.Thread(daemon = true, target = ...) — now parses, and emits threading.Thread(daemon=True, target=lambda: f(None)) in one call (verified end to end with a running thread). An all-unit type still rejects a slot with the existing message.

The mid-list None → dropped change is breaking in principle; nothing in the repo, lessons or tests relied on it, and the issue makes the design argument (int -> unit -> int -> int most plausibly meant max(3, 5)).

Docs: DESIGN.md §6 states the one rule and the amended slot arithmetic; ROADMAP.md item 24. Also carries the VS Code Marketplace ledger line to 0.8.0 (uploaded 2026-08-30), per the one-ledger-PR rule.

Closes #123

)

A leading unit in a multi-parameter extern fired the nullary
zero-argument-call rule and applied the remaining arguments to the
result (abs()(-5)), while a mid-list unit travelled as None
(max(3, None, 5)): three shapes, three meanings. Now one rule — a unit
parameter contributes no Python argument. The nullary call is unchanged,
abs(-5) and max(3, 5) fall out, partial application closes over a
lambda that accepts and ignores a future unit, receiver methods and
bare references drop units the same way, and the parser's ...-slot
arithmetic counts only non-unit arguments, so
threading.Thread(daemon = true, target = ...) behind a leading unit now
parses and emits a single call.

Also carries the VS Code Marketplace ledger line to 0.8.0 (uploaded
2026-08-30), per the one-ledger-PR rule.

Closes #123
@simontreanor
simontreanor merged commit 8631378 into main Aug 31, 2026
16 checks passed
@simontreanor
simontreanor deleted the fix/unit-extern-params branch August 31, 2026 06:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A leading unit parameter in a multi-parameter extern splits the call: unit -> int -> int = abs emits abs()(-5)

1 participant