flan/conditions.org
Joseph Ferano 91d1368279 Say in the specs what the break loop actually does now
spec-conditions.md §4 gains the rule the shadowing bug was hiding: a handler
matches by name, a debugger identifies by position, and the two are not the
same question. With it, the snapshot — a position means nothing against a
stack that moves — and the fact that a visible restart may still be
unreachable, which §6's explicit lowering makes possible.

§3 records the open one: a clause should carry a report string.
is what invoke-restart needs and not what a person reading a list needs. It
wants settling before restarts with parameters, which is where a bare name is
least sufficient.

conditions.org had the break loop under "Not yet", which it has not been for
some time, and now says why find-restart and compute-restarts still are: they
are blocked on a Restart type and a list to return one in, not on effort.
2026-09-12 04:58:42 +07:00

3.0 KiB

Conditions and restarts — cheatsheet

Why it is shaped this way: spec-conditions.md. Something to poke at: conditions-play.flanflan dev conditions-play.flan, then C-c C-c.

Works

(signal c)                  ; Unit. Handler returns -> carry on. No handler -> no-op.
(error  c)                  ; Never. Only a transfer gets past; else the program stops.

(handler-bind [(Type [c] body ...) ...] body ...)     ; match by type, no hierarchy

(restart-case BODY          ; BODY and every clause have the same type = the form's
  (name [] CLAUSE) ...)

(invoke-restart 'name)      ; Never. Innermost frame offering the name wins.
(defn fetch [n i32] i32
  (restart-case (middle n)              ; its value if nothing transfers
    (use-placeholder [] -1)
    (retry           [] 7)))

(handler-bind [(AssetMissing [c] (invoke-restart 'use-placeholder))]
  (fetch 2))                            ; -1

defer between the invoke and the target runs, innermost first, before the clause body. errdefer does not.

The break loop

An unhandled error in a dev build stops on the frame that erred, with nothing unwound, and waits. C-c C-b in Emacs lists what is on offer and resumes into the choice; flan:stopped(Missing) in the modeline says it happened.

The list is numbered, and the number is what is chosen. Two frames offering retry both appear and §4's by-name walk can only ever reach the first, so a name cannot say which one is meant — restart-at can.

A restart below the evaluation a break is inside is listed, marked, and refused: C-x C-e runs its thunk through a C frame that holds its own transfer channel, so an unwind aimed past it would stop at the thunk. Choose one offered above it, or abort.

Not yet

handler-case · find-restart · compute-restarts · restarts with parameters. Each refused by name with its reason.

find-restart and compute-restarts are blocked on a type rather than on effort: §4 gives them (Option Restart) and a list, and there is no Restart type and no list to return one in. The break loop reads the same stack through the agent's socket instead.

Gotchas

  • A handler closes over nothing. It is lifted into its own function. Accumulate into a global, or put the value on the condition.
  • A restart re-runs whatever sits between it and the target. Control resumes at the restart-case, so a retry repeats side effects after it. Put the restart-case where re-entry is safe.
  • An unknown restart name is a hard stop. No find-restart to test with.
  • No supertype, so nothing can say "any condition".
  • signal cannot hand a value back. Deliberate (§1).
  • A condition must be a struct. return is refused inside either form.

Stops the program, exit 134

unhandled AssetMissing
file.flan:3:25: no restart named nope is active
file.flan:4:7: a defer invoked a restart, which a defer may not do — ...