State, mutation, and managed resources

tulam is strict and functional by default, but it is not restricted to immutable applications. Mutation is represented by explicit types and effects so its scope and providers remain visible.

State as an effect

The parameterized state contract is:

effect State(s:Type) = {
    function get() : s;
    function put(item:s) : Unit;
    function modify(f:s -> s) : Unit
};

A handler can interpret state with a reference:

handler RefState(initial:s) : State(s) = {
    let cell = newRef(initial);
    function get() = readRef(cell);
    function put(item) = writeRef(cell, item);
    function modify(f) = modifyRef(cell, f)
};

The local reference is encapsulated by the handler installation rather than becoming invisible global state.

References and mutable arrays

The Mutable library exposes reference and mutable-array operations through native providers. Their types distinguish mutable storage from ordinary persistent records and arrays.

A record update such as point { x = 2.0 } constructs a new value; it does not mutate point. Mutation requires the relevant mutable abstraction and effect.

Resource finalization

Files, handles, and other resources belong in scopes with finally behavior. Deep finalization is part of the handler contract, including cancellation and recoverable failure. Multi-shot continuations may not duplicate a non-duplicable resource without explicit evidence.

Console and files

Console and FileIO are effects with default native handlers. Their use stays in the result row:

action copy(path:String, destination:String)
    : Eff { file:FileIO } Unit = {
    text <- readFile(path);
    writeFile(destination, text)
};

Ownership direction

The language core records runtime relevance and supports typed resource APIs. More advanced ownership, cross-target residence, and concurrent access remain library/evidence concerns rather than unchecked aliases.

Current implementation

Native reference and mutable-array providers have representative tests. Generic provider ABI validation and concurrency-aware ownership are not yet complete across future targets.

There is also a current library/reference discrepancy: lib/Effect.tl still declares an unparameterized State whose operations use an implicit a, while the approved effect design and the Language Reference’s handler example use State(s). The parameterized form above is the normative direction; consult the generated standard-library reference when calling the bundled library until the declaration is reconciled.

Common mistakes

A record update is not mutation, a reference operation is not pure, and a multi-shot continuation cannot duplicate an owned resource without evidence. Keep resource lifetime inside the handler or scope that guarantees cleanup.

Recap

State and mutable storage are explicit capabilities layered on a strict functional core. Their types make scope, provider, and cleanup obligations visible.

Further semantics: Language Reference §17 and Effect Design.