flan/conditions.org
Joseph Ferano 5184d732c9 Say what a typed restart still cannot do, and who has to do it
The language half of §3's parameters is in; the half that makes it worth
having is not. A break loop chooses by position and has nothing to fill a
clause's parameters in with, and that is now the top item in NEXT.md,
spelled out end to end — the accessors the frame can already answer, the
signature on the wire, and the one store that has to happen before the
channel is aimed.

§3 asks for a clause's report string to be settled before parameters and
it was not. The field is cheap and so is the accessor; the only thing
that would read either is the break loop's listing, which lives in the
agent and the daemon, so it would have shipped as a field nothing read.
It belongs with the editor half, which is changing that listing anyway.
2026-09-12 10:48:21 +07:00

104 lines
4.2 KiB
Org Mode

#+TITLE: Conditions and restarts — cheatsheet
#+STARTUP: showeverything
Why it is shaped this way: [[file:spec-conditions.md][spec-conditions.md]]. Something to poke at:
=conditions-play.flan==flan dev conditions-play.flan=, then ~C-c C-c~.
* Works
#+begin_src lisp
(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 [p T ...] CLAUSE) ...)
(invoke-restart 'name arg ...) ; Never. Innermost frame offering the name wins.
#+end_src
#+begin_src lisp
(defn supplied [n i32] i32
(restart-case (middle n)
(use-value [v i32] (* v 2)) ; the answer comes from outside
(retry [] 7)))
(handler-bind [(AssetMissing [c] (invoke-restart 'use-value 21))]
(supplied 7)) ; 42
#+end_src
A clause's parameters are slots of the function that wrote it, and the invoker
fills a buffer that function owns — by the time a clause runs, the invoking
frame has gone. What a clause takes is compared with what was given at *run
time*, count then spelling, because a restart is found by name on a dynamic
stack and neither end can see the other.
#+begin_src lisp
(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
#+end_src
~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~ · a clause's report
string. Each refused by name with its reason.
A restart *with parameters* cannot be taken from the break loop: it aims at a
frame by position and has nothing to fill the parameters in with, so the clause
stops the program rather than running on values no one supplied. Choose one
that takes none, or ~abort~.
~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.
- *So are the wrong arguments*, and for the same reason: nothing static can
know what a name will find. The message names both signatures.
- *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
#+begin_example
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 — ...
file.flan:9:12: restart use-value takes (i32), given (string)
file.flan:6:5: restart use-value takes (i32), and whatever took it supplied
no arguments — ...
#+end_example