the published fragment. -->
Dynamic operations for a calculator
Every arithmetic operation is a plugin. Adding one more plugin adds another operation to the calculator; taking one out should make it forget. Toggle the operations below and type an expression — both runtimes calculate the same expression, and only one of them does it accurately.
The lab
Five primitives and one derived operation. % is a remainder, and a remainder is
defined as a - floor(a / b) * b — so it works only for as long as
subtraction, multiplication and division all do. Turn off division and watch what each calculator
goes on offering.
Both sides below are an interactive model of examples/run_calculator.py, which runs
the same comparison against the real runtime and prints the same numbers.
Drive the two calculators yourself. The walkthrough above never touches these controls, so anything you change here is your own experiment - press Restart to put the walkthrough back in charge.
Why it diverges
Not a straw man
When division is unregistered, its teardown() removes it from all four of the
calculator's tables — the operator table, the precedence table, the tokenizer's alphabet
and the catalog help reads. There is no forgotten line.
What it cannot do is tell % that the division it resolved at registration has gone
away. require resolved once and handed back a direct reference; nothing invalidates
it later, because nothing is tracking it. So the remainder stays registered, stays advertised, and
fails the next time it is asked to divide.
Declarations
On the cordis side the remainder declares inject=["op.sub", "op.mul", "op.div"]. That
is the whole of it. Retiring division changes the fiber's target, the runtime deactivates the
remainder, and its catalog entry goes with it — so the calculator stops offering a
remainder the moment it stops being able to compute one.
The memo cache is the sting. It is opened while evaluating an expression, long after setup returned, so a hand-written teardown cannot reach it — and on the conventional side it survives to answer, correctly, for exactly the pairs it happens to hold.
Run it for real
The walkthrough is modelled on tests/UAT/calculator.sh, which runs the same sequence
against the real runtime and asserts each step.
Its companion page, docs/demo.html, makes the same argument by counting what a retired
component leaves behind in a live process. (Named rather than linked: the two pages sit beside each
other in a clone, but are published at separate addresses.)
cordispy is a Python realization of the runtime described in A Programming Paradigm for
Spatiotemporal Composability (Shi, Zhang, Cui). Both calculators here run the same arithmetic out
of the same shared engine (examples/calc/engine.py); only composition differs. The
conventional side is a faithful implementation with no planted bug — see
examples/calc/naive_side.py, whose header says exactly which two properties of the design
every difference follows from.