- Joined
- Jul 20, 2025
- Messages
- 145
- Thread Author
- #1
Governed Model of Program Execution
Most introductory programming courses teach execution like: a program runs from top to bottom, performing calculations and occasionally interacting with the outside world—printing text, writing files, sending network requests. In many languages, calculation and action are interwoven. I propose a different structure. I will seperate calculation from action and insert a governing runtime between them. The result is a deterministic, auditable model of execution implemented in Haskell. To understand the design, it is helpful to clarify two importnat ideas:
A pure function is a function that only computes a result from its inputs. It does not modify files, read from the network, print to the screen, or depend on hidden global state. Given the same inputs, it always returns the same output. In contrast, an effect (short for “side effect”) is an action that changes or observes the external world such as writing a file, sleeping for a second, or printing a line.
Most programs mix these freely. I won't. Effects will be treated as descriptions of actions of possible actions, and actually performing them will be postponed until a later, controlled stage.
Separating Calculation from Action
My language evaluates expressions into values. Crucially, evaluating an effect does not perform it. It produces a value that describes it.
In the evaluator:
When the program encounters something like “emit this effect,” the interpreter returns a VEffect value. It does not run the effect and the interpretator remains purely computational.
After evaluation finishes, the system collects all the effect descriptions that were produced:
This function walks through the final values of the program and extracts the list of EffectDescription structures. Everything else is ignored. At this point, the program has not changed the world. It has only described the changes it would like to make.
Why Treat Effects as Data?
Treating effects as data provides several advantages:
An effect's identity depends on what it is. If two effect descriptions are structurally identical, they have the same ID. This makes effects content-addressed. Identity is not based on when they are created or where they appear in the program. It is based solely on structure.
Dependency Graphs and Deterministic Scheduling
Effects may depend on other effects. For example, “write to file B” might depend on “create directory A.” These dependencies are explicitly recorded. The runtime constructs a dependency graph and executes effects in layers. Effects with no unmet dependencies form a “wave” and may run in parallel:
All effects in a layer are executed concurrently. After execution, the results are sorted by `EffectID' before being recorded. This ensures that, even though execution is parallel, the historical record is deterministic.
If some effects cannot run because their dependencies failed, they are rejected. Nothing is silently retried or implicitly reordered.
Authorization Before Execution
Before executing an effect, the runtime checks its historical record. If an identical effect (same `EffectID') has already succeeded, it is not executed again.
If the effect has already been completed successfully, the decision is “reject.” Otherwise, it may proceed.
This guarantees idempotence. The key architectural point here is that the component deciding whether an action may occur is different from the component that knows how to perform it.
Executing and Verifying Effects
Each type of effect has an associated specification:
The runtime:
The Ledger
Every attempted effect is appended to a persistent ledger stored in SQLite.
Each entry records:
Language-Level Constraints
The language further enforces discipline by restricting what effect definitions may contain. Effect bodies must be simple, they cannot contain branching (if), local bindings (let), or nested functions.
For example:
If an effect body includes any of these constructs, elaboration fails.This restriction ensures that effects are declarative descriptions, not mini-programs with hidden logic.
Decision-making remains in the pure computation layer or in the governing runtime.
Conclusion
This architecture reframes program execution. Instead of:
The separation increases transparency and determinism. It becomes possible to inspect all intended changes before they occur. Execution history is stable and replayable. Duplicate actions are automatically suppressed.
In contrast to conventional interpreters, where IO can occur at any point in evaluation, this design introduces structure and governance.
Most introductory programming courses teach execution like: a program runs from top to bottom, performing calculations and occasionally interacting with the outside world—printing text, writing files, sending network requests. In many languages, calculation and action are interwoven. I propose a different structure. I will seperate calculation from action and insert a governing runtime between them. The result is a deterministic, auditable model of execution implemented in Haskell. To understand the design, it is helpful to clarify two importnat ideas:
A pure function is a function that only computes a result from its inputs. It does not modify files, read from the network, print to the screen, or depend on hidden global state. Given the same inputs, it always returns the same output. In contrast, an effect (short for “side effect”) is an action that changes or observes the external world such as writing a file, sleeping for a second, or printing a line.
Most programs mix these freely. I won't. Effects will be treated as descriptions of actions of possible actions, and actually performing them will be postponed until a later, controlled stage.
Separating Calculation from Action
My language evaluates expressions into values. Crucially, evaluating an effect does not perform it. It produces a value that describes it.
In the evaluator:
Haskell:
evalExpr _ (Emit ed) = Right (VEffect ed)
When the program encounters something like “emit this effect,” the interpreter returns a VEffect value. It does not run the effect and the interpretator remains purely computational.
After evaluation finishes, the system collects all the effect descriptions that were produced:
Haskell:
collectEffects :: [Value] -> [EffectDescription]
collectEffects [] = []
collectEffects (VEffect ed : vs) = ed : collectEffects vs
collectEffects (VList subVs : vs) = collectEffects subVs ++ collectEffects vs
collectEffects (_ : vs) = collectEffects vs
Why Treat Effects as Data?
Treating effects as data provides several advantages:
- Predictability: The interpreter is entirely deterministic. It only computes values.
- Inspectability: Before anything runs, the full set of requested actions is visible.
- Control: Another component can decide which actions should actually occur.
Haskell:
EffectID = SHA256(canonicalEffectBytes)
Dependency Graphs and Deterministic Scheduling
Effects may depend on other effects. For example, “write to file B” might depend on “create directory A.” These dependencies are explicitly recorded. The runtime constructs a dependency graph and executes effects in layers. Effects with no unmet dependencies form a “wave” and may run in parallel:
Haskell:
currentEntries <- mapConcurrently realizeTransition ready
return (sortOn heEffectID currentEntries ++ nextEntries)
If some effects cannot run because their dependencies failed, they are rejected. Nothing is silently retried or implicitly reordered.
Authorization Before Execution
Before executing an effect, the runtime checks its historical record. If an identical effect (same `EffectID') has already succeeded, it is not executed again.
Haskell:
governorAdmit :: History -> EffectDescription -> Decision
This guarantees idempotence. The key architectural point here is that the component deciding whether an action may occur is different from the component that knows how to perform it.
Executing and Verifying Effects
Each type of effect has an associated specification:
Haskell:
data EffectSpec = EffectSpec
{ esRun :: EffectDescription -> IO Outcome
,esVerify :: EffectDescription -> IO Outcome
}
The runtime:
- Decides whether the effect is allowed.
- Runs it if permitted.
- Verifies the result.
- Records the outcome in a ledger.
The Ledger
Every attempted effect is appended to a persistent ledger stored in SQLite.
Each entry records:
- The time of execution
- The effect’s unique ID
- Its dependencies
- Whether it was admitted or rejected
- Whether it succeeded or failed
Language-Level Constraints
The language further enforces discipline by restricting what effect definitions may contain. Effect bodies must be simple, they cannot contain branching (if), local bindings (let), or nested functions.
For example:
Haskell:
isControlFlow (Seq _) = True
isControlFlow (Let _ _ _) = True
isControlFlow (Fun _ _ _ _) = True
isControlFlow (If _ _ _) = True
If an effect body includes any of these constructs, elaboration fails.This restriction ensures that effects are declarative descriptions, not mini-programs with hidden logic.
Decision-making remains in the pure computation layer or in the governing runtime.
Conclusion
This architecture reframes program execution. Instead of:
My language enforces: :Compute and act simultaneously
- Compute desired actions (purely).
- Identify and organize them.
- Decide which are admissible.
- Execute them in a controlled order.
- Verify and record results immutably.
The separation increases transparency and determinism. It becomes possible to inspect all intended changes before they occur. Execution history is stable and replayable. Duplicate actions are automatically suppressed.
In contrast to conventional interpreters, where IO can occur at any point in evaluation, this design introduces structure and governance.