Fundamentos
Nesta página
Um efeito é uma interface de operações impuras. O bloco effect declara apenas
assinaturas — sem corpos. Uma função que usa o efeito anota with NomeDoEfeito na
assinatura e invoca cada operação com perform. Quem chama a função decide o
significado real de cada operação dentro de um bloco handle … with { … }.
O exemplo mais simples tem um único efeito com uma única operação. O handler fornece
a única cláusula necessária e o valor da expressão da cláusula é o que perform devolve:
effect IO com uma operação; handler devolve uma string padrão.
// Feature: algebraic effects — declare, perform, handle
// Syntax: `effect Name { fn op(...) -> T }` declares; `perform Name::op(...)`
// raises; `handle expr with { Name::op(args) => ... }` interprets.
// When to use: dependency-inject impure ops (IO, logging, time,
// randomness) without dragging trait objects through every call.
effect IO {
fn read(path: str) -> str
}
// `with IO` says: this function performs at least one IO effect.
fn read_config() with IO -> str {
return perform IO::read("config.toml")
}
// The handler block decides what `IO::read` actually means.
let result = handle read_config() with {
IO::read(path) => "default config from {path}",
}
print(result)
// expected: default config from config.toml
Um efeito pode agrupar várias operações relacionadas — por exemplo, um Logger com
info e warn. O handler precisa cobrir todas as operações que o corpo possa invocar:
Logger com info/warn; o handler despacha cada uma para print.
// Feature: effect with multiple operations
// Syntax: an `effect` block can declare several `fn` operations;
// the handler must provide a clause for every operation that
// the effectful code may `perform`.
// When to use: a coherent capability — e.g. a Logger with `info`/`warn`/`error`
// — that you want to swap atomically (test logger, file logger, …).
effect Logger {
fn info(msg: str)
fn warn(msg: str)
}
fn run() with Logger {
perform Logger::info("starting")
perform Logger::warn("careful")
perform Logger::info("done")
}
handle run() with {
Logger::info(msg) => print("[INFO] {msg}"),
Logger::warn(msg) => print("[WARN] {msg}"),
}
// expected:
// [INFO] starting
// [WARN] careful
// [INFO] done
As cláusulas do handler recebem os argumentos da operação como parâmetros nomeados.
O valor produzido pela expressão da cláusula é retornado ao perform como resultado —
o que permite mocks determinísticos e stubs computados:
Efeito Math com add e double; handler calcula o resultado real a partir dos argumentos.
// Feature: handler clauses can use the operation arguments
// Syntax: each clause `Op(arg1, arg2) => <expr>` binds the args
// and the expression's value is the result of `perform`.
// When to use: any effect where the result depends on the input —
// pure mocks, deterministic stubs, computed responses.
effect Math {
fn add(a: int, b: int) -> int
fn double(n: int) -> int
}
fn compute() with Math -> int {
let x = perform Math::add(2, 3)
let y = perform Math::double(x)
return y
}
let result = handle compute() with {
Math::add(a, b) => a + b,
Math::double(n) => n * 2,
}
print(result)
// expected: 10
Desafio
No exemplo de Logger, adicione uma terceira operação error(msg: str) ao efeito e
uma cláusula correspondente no handler. O que acontece se você omitir a cláusula?
Veja também