Errors
An error aborts the current top-level form: the machine unwinds, the REPL reports, and the next form starts fresh. Part I has already met the main kinds —
- parse errors, including unbound variables, caught before evaluation starts;
- type errors, a primitive applied to the wrong kind of value;
- staging errors, violations of the staging discipline (Part II).
(cdr 'a)
;! type error in cdr: expected Tup, got Sym("a")
throw
(throw v) raises the value of v as an error with a first-class
payload — any value, typically a symbol or a structured list saying
what went wrong:
(throw 'out-of-cheese)
;! uncaught throw: 'out-of-cheese
The payload rides the unwind intact, which makes throw a signalling
mechanism rather than just a panic: a program can throw structured
data and something below can take it apart.
catch
The something below is catch. (catch e) evaluates e and returns
a tagged list either way: (ok value) if no error escapes, (error payload) if one does — a result the caller can dispatch on with car
and eq?:
(catch (+ 1 2))
;=> ('ok 3)
(catch (throw 'boom))
;=> ('error 'boom)
(catch (throw (cons 'unbound (cons 'x nil))))
;=> ('error ('unbound 'x))
A thrown payload arrives structurally. Any other error inside a
catch — a type error, say — arrives with its report flattened into a
single symbol:
(catch (car 5))
;=> ('error 'type error in car: expected Tup, got Cst(5))
catch exists for one caller: the Purple REPL loop, which wraps each
evaluation in it so that a user error prints a message instead of
killing the session, and so that a base environment can report an
unbound variable loudly as (unbound . name) rather than returning a
silent default. It is deliberately minimal — no handler argument, no
filtering, no rethrow — and user code is expected to reach for the
session-level error handling of Part IV rather than call it directly.
The floor takes the same view of catch as of its other effects:
provide the smallest primitive the layer above needs, and let the
layer above build the ergonomics.