Saltar al contenido principal

effect

Since v2.87.0 · Top-level declaration

Grammar​

effect <Name> {
<Op>(<param>: <Type>, ...) -> <ReturnType>
<Op>() -> Never # an operation that never resumes
...
}

Declares a named set of operations that a flow may raise with perform and an enclosing handle may interpret.

The property this buys is one nothing else in the language expresses: a flow performs an operation it knows nothing about, and code somewhere else decides what that operation means — without the flow being changed, recompiled, or even aware a handler exists.

effect Delivery {
Emit(token: Text) -> Unit
Done() -> Never
}

Delivery mentions no transport. A flow that performs Emit has no opinion about whether the token reaches a websocket, a log file, or a test harness.

Surface​

An effect declaration is top-level, beside flow and type. Each line inside declares one operation: a name, a typed parameter list, and a return type.

The return type is what the perform site receives when a handler clause calls resume(v). Unit means the site resumes with nothing useful; Never means the operation cannot resume at all — a clause for it may not call resume, and nothing after the perform runs. Done() -> Never is the idiomatic terminator.

Fields​

The catalog is closed, and that is what makes a bare perform Emit(x) resolvable: exactly one declared effect owns Emit.

If two effects both declared Emit, the compiler does not pick one. It refuses the bare form and names both candidates (axon-T964), and you write the qualified form perform Delivery.Emit(x) instead. The reason is worth stating plainly: a token routed to the audit log instead of the wire is a bug that produces plausible output, and those are the ones nobody finds.

Runtime behaviour​

The declaration itself emits no code. It gives the type checker the operation signatures it needs to check three things:

CheckWhat it refuses
axon-T964a bare perform Op(…) when more than one effect declares Op — names both
axon-T966a perform that reaches an entry point with no handler in scope. Interprocedural, because the performing flow and its handler are usually different flows — which is the whole point

There is no runtime fallback for an unhandled effect. An effect with nowhere to yield to is a compile error, so the program that would discover it in production does not build.

What this primitive is NOT​

Not the effects: row on a tool declaration. That is a different feature that shares a word: effects: <io, network> declares which side effects a tool performs, for the linearity and governance rules. This page is about Plotkin/Pretnar algebraic effects with handlers and continuations.

Not exceptions. An exception unwinds; an effect yields, and the handler decides whether to hand control back. resume(v) returns to the exact perform site with v as its value and the deliberation continues.

Not an interface or a trait. There is no implementing type. The handler is attached by lexical scope at the call site, not by the value being operated on.

See also​

  • perform — raise one of these operations
  • handle — interpret them within a delimited scope
  • examples/algebraic_effects.axon — the complete program these three pages describe, compiled by the test suite
  • papers/paper_algebraic_effects_streaming.md — the theory