Results¶
Python collapses two different kinds of failure into one mechanism.
raise/try/except handle both "this call can fail in an
ordinary, expected way" — a missing key, a parse failure — and "something
is broken" — a violated invariant, a bug — and because exceptions carry no
signature, nothing about a function's type distinguishes the two. Java
tried to fix the visibility problem by making exceptions checked —
declared in throws, enforced by the compiler — and got the goal right
and the mechanism wrong: checked exceptions do not compose with generics
or lambdas, and one new exception type deep in a call chain forces every
intermediate signature to change, which is why nearly every language
designed since has rejected the mechanism while still agreeing with what
it was reaching for.
Lucid splits the two kinds of failure instead of choosing one mechanism for both: this covers the expected, recoverable kind; Exceptions covers the other.
Recoverable errors are ordinary return types¶
An expected, recoverable failure is just part of what a function returns — a union, handled the same exhaustive way any other union is:
type ParseResult = Value | ParseError
def parse(text: str) -> ParseResult:
...
match parse(text) as result:
case Value:
...
case ParseError:
...
ParseResult is an ordinary union change,
not something every caller up the chain has to redeclare.
Matching every fallible call by hand does not scale —
The ? operator covers the sugar for
propagating one of these without a match at every call site.