Want to contribute? Fork us in GitHub!

The Terminator

Four years ago we introduced the error and try objects: error wrapped an object and threw it up the stack, try caught it, extracted the payload, and ran a finally block. It worked, but it was Java smuggled into EO — 𝜑-calculus has no exceptions to throw and no stack to unwind. What it does have is , the bottom — a computation that terminated. Recently we deleted try and error and gave the bottom a spelling of its own: the letter T, which we call the terminator.

A Value That Detonates

The new syntax element is one reserved glyph, mapped to in XMIR:

T > x

The terminator is a value with no data and no behaviour. It may be named, stored, copied, and passed around like any other object; a copy of it is itself, and every attribute taken from it is itself again, so it flows silently through dispatch and application. It detonates only when something forces it — tries to dataize it. Then the program stops, for good: no object catches a forced bottom, because catching is not a thing anymore.

An Epitaph, Not a Payload

A terminator may carry a cause:

T "this can't happen"

The first object applied to a bottom is remembered and printed when the bottom is finally forced. That’s all the cause does. EO code can’t read it back, match on it, or re-throw it — it is not an exception payload, it is an epitaph.

This is how the tuple object defines its empty companion:

tuple > empty
  T
    "Can't get tail from the empty tuple"
  T
    "Can't get head from the empty tuple"
  0

The head of the empty tuple is a terminator. Building the tuple costs nothing and fails nothing; only the code that forces empty.head dies with the message above.

Absent Means Bottom

Taking a void attribute that nobody filled now yields a terminator instead of raising an error. This one rule replaces the whole optional-argument machinery. Consider i64.div:

[n x cant-divide] > div
  if. > @
    x.as-bytes.eq 00-00-00-00-00-00-00-00
    cant-divide
      "i64 cannot be divided by zero"
    ...

The third void, cant-divide, is a fallback the caller may supply. If they don’t, taking it yields , the message lands in its cause slot, and division by zero terminates the program:

2.as-i64.div 0.as-i64

If they do, the same application invokes their handler instead:

2.as-i64.div
  0.as-i64
  "no luck" > [message]

This dataizes to "no luck". One mechanism, two behaviours, zero exceptions.

One Escape Hatch

Sometimes a failure is expected and the show must go on. For that single purpose there is the recovered atom:

recovered
  2.as-i64.div 0.as-i64
  7

It resolves the first attribute to its normal form; if that turns out to be a bottom, it behaves as the second, otherwise as the first. This dataizes to 7. Note what recovered is not: it sees no cause, filters no “exception types,” runs no finally. When both attributes terminate, the result is still a bottom, which only an outer recovered may intercept — a bottom that nobody recovers ends the program.

Tests Expect It Too

Since termination is now a first-class outcome, unit tests assert it with the throwing-test suffix -->, a sibling of the truthy ++>:

2.as-i64.div 0.as-i64 --> stops-on-division-by-zero

The bottom was in 𝜑-calculus from the very first paper; try and error never were. Now the language agrees with its own semantics, and the price of that agreement is one letter.

That’s all for today.