Authoritative source: Actions and sequencing

Actions and sequencing

Actions provide readable syntax for ordered effectful computations. They do not hide the effect row or introduce an untyped statement language.

Declaring an action

action main() : Eff { console:Console, file:FileIO } Unit = {
    path = "input.txt";
    text <- readFile(path);
    putStrLn(text)
};

An action may carry requires and on clauses in the same order as a function.

Statement forms

x <- effectful;
x = pure;
effectful;
finalExpression

Semicolons separate statements. Later statements see earlier bindings.

Why distinguish <- and =?

The distinction communicates whether obtaining a value itself requires a capability:

name = "Ada";
line <- readLine();

The first is ordinary pure data. The second requests a console operation. The checker preserves that difference even after action syntax desugars.

Desugaring

Actions elaborate into typed sequencing/bind operations while preserving effect rows and handler scope. The surface block is convenient notation, not a second imperative runtime.

Returning values

action ask() : Eff { console:Console } String = {
    putStrLn("Name?");
    name <- readLine();
    name
};

The final name checks against String. A Unit action commonly ends with an operation that returns Unit.

Error feedback

Using <- with a pure value, allowing an undeclared effect to escape, or producing the wrong final type is rejected during checking. An action is still an expression and can be handled, passed, or returned where its effect type permits.

Common mistakes

Use <- only for the produced value of an effectful computation and = for a pure local binding. Separate statements with semicolons, and remember that the last expression determines the action result.

Recap

Actions are typed sequencing notation. They make evaluation order readable while preserving effects, lexical scope, and the ordinary expression result.

Normative details: Language Reference §14.