Effectful functions and operations

Effects describe capabilities a computation may use. They remain part of the type instead of being inferred as invisible global behavior.

Declaring an effect

effect Console = {
    function readLine() : String;
    function putStrLn(text:String) : Unit
};

An effect declaration is a contract. Operations may be parameterized and their types follow ordinary binder and universe rules.

Effectful result types

function greet() : Eff { console:Console } Unit = action {
    name <- readLine();
    putStrLn(concat("Hello, ", name))
};

The row label console identifies one installation requirement. Effects may be inferred when omitted, but exported declarations should normally state their contract.

Open effect rows

function twice(f:a -> Eff { | e } a, item:a)
    : Eff { | e } a = ...;

The row variable e preserves whatever effects the supplied function uses. This gives higher-order code effect polymorphism without claiming purity.

Qualified operations

If two installed effects expose the same operation name, qualify it by row label:

function compare()
    : Eff { left:LocalState, right:RemoteState } Bool = action {
    a <- left.read();
    b <- right.read();
    a == b
};

Resolution is static. If the left side names an ordinary lexical value, normal field or method resolution takes precedence; operation dispatch never becomes dynamic string lookup.

Purity

Eff {} a is observationally pure and can usually be written a. Handling an effect can remove its row label, while effects used by the handler are added to the result.

Resumption bounds

Effects are affine by default: an operation continuation may be resumed zero or one time. Multi-shot behavior requests ordinary Resumption(Many) evidence and requires suitable target support.

Current implementation

Effect rows, qualified operations, deep lexical handlers, return/finalization clauses, default providers, mutation providers, and representative resumptions have native coverage. Lexical handlers across recursive calls remain a named backend gap.

Common mistakes

Do not erase an open row tail in higher-order code, assume a default handler makes an effect pure, or qualify an operation by a value name that already has ordinary field meaning. Ambiguous operation identity is a static error.

Recap

An effect row states which installations a computation requires. Labels make installations distinct, open tails preserve unknown capabilities, and handlers transform rows explicitly.

Normative details: Language Reference §13.1–13.2.