A handler's capture is the non-escaping kind

The refusal said milestone 5, which lumped it in with escaping closures - and
plan.org has just deferred those until a concrete use case appears. A handler
frame does not outlive the function that pushed it, so what a handler clause
needs is spec-memory.md's case 2, a non-escaping fn capturing by value into a
stack environment, which is settled rather than deferred.

Worth recording before anyone schedules it, because open decision #5 says in as
many words that without this conditions are not worth building, and the
deferral it just received does not apply to it.
This commit is contained in:
Joseph Ferano 2026-09-11 12:24:05 +07:00
parent 5980b5b60f
commit c322ef60bf
2 changed files with 20 additions and 10 deletions

16
NEXT.md
View File

@ -923,10 +923,18 @@ Three decisions worth keeping:
Which gives the two refusals, both by the house rule rather than by accident:
- **A handler cannot see the establishing function's locals.** That is a
closure with an explicit environment — milestone 5's work — so a reference to
one is refused *for that reason* rather than reported as an unknown name.
Globals and the condition are in scope, which is what the accumulation case
needs.
closure with an explicit environment, so a reference to one is refused *for
that reason* rather than reported as an unknown name. Globals and the
condition are in scope, which is what the accumulation case needs.
What it needs is narrower than it looks, and worth getting right before
anyone schedules it: a handler frame does not outlive the function that
established it, so this is spec-memory.md's **case 2** — a non-escaping `fn`
capturing by value into a stack environment — and *not* the escaping closure
that plan.org's open decision #5 defers until a concrete use case. Case 2 is
settled, and #5 says in as many words that without it "conditions are not
worth building". So the biggest usability limit in conditions is not behind
the thing that was just deferred.
- **`return` inside a `handler-bind` body is refused.** The frames are popped
on the way out and an early exit would leave them on the stack pointing into
a function that has gone. Same shape as `defer` inside a block.

View File

@ -126,9 +126,11 @@ let lookup ctx name = List.assoc_opt name ctx.scope
(* A handler clause is lifted into a function of its own, so the establishing
function's locals are simply not there. Capturing them is a closure with an
explicit environment milestone 5's work and until it exists a reference
to one is refused for the reason it is really refused for, rather than as a
name nobody has heard of. *)
explicit environment spec-memory.md's case 2, a non-escaping [fn] capturing
by value into a stack environment, since a handler frame does not outlive the
function that pushed it and until that exists a reference to one is refused
for the reason it is really refused for, rather than as a name nobody has
heard of. *)
let captured ctx loc name =
if ctx.in_handler && List.mem_assoc name ctx.outer then
raise
@ -539,9 +541,9 @@ and block ctx ?want loc body =
function of its own and reached through a pointer.
Which means it cannot see the establishing function's locals. Capturing them
is a closure with an explicit environment, which is real work and is
milestone 5's; until then a reference to one is rejected by name rather than
silently resolving to something else. Globals and the condition itself are
is a closure with an explicit environment the non-escaping kind, captured
by value onto this frame and until that exists a reference to one is
rejected by name rather than silently resolving to something else. Globals and the condition itself are
in scope, which is enough for the accumulation case §1 is about.
The body may not [return] either. The frames are pushed and popped around