Saltar al contenido

Múltiples Efectos

En esta página

Una función puede declarar más de un efecto separándolos con + en la cláusula with. El handler instalado debe cubrir las operaciones de todos los efectos que el cuerpo pueda invocar — pero cada efecto sigue siendo una unidad independiente e intercambiable:

with IO + Logger; el handler cubre ambos en un único bloque.

04-multi-effect.zolo
Playground
// Feature: multi-effect signature — `with E1 + E2`

// Syntax: a function may declare more than one effect at once;

// the handler must cover every operation that the body performs.

// When to use: a workflow that mixes capabilities — e.g. read input

// (IO) and trace progress (Logger) — without coupling them into one effect.


effect IO {
  fn read(path: str) -> str
}

effect Logger {
  fn log(msg: str)
}

fn process() with IO + Logger -> str {
  perform Logger::log("loading")
  let data = perform IO::read("file.txt")
  perform Logger::log("loaded")
  return data
}

let result = handle process() with {
  IO::read(p) => "<contents of {p}>",
  Logger::log(msg) => print("LOG: {msg}"),
}
print(result)
// expected:

//   LOG: loading

//   LOG: loaded

//   <contents of file.txt>

Los handlers se resuelven léxicamente: un handle interno para el mismo efecto sombrea al externo hasta que su ámbito termina. Esto permite sobreescribir partes del comportamiento en sub-llamadas sin reconstruir el handler completo:

El handler externo devuelve "OUTER(…)"; el handler interno anidado devuelve "NESTED" para la misma operación.

05-nested-handlers.zolo
Playground
// Feature: nested handlers — inner shadows outer for the same effect

// Syntax: handlers compose lexically; an inner `handle ... with` for

// the same operation takes over until that block exits.

// When to use: scoped overrides — e.g. inside a test block, swap the

// outer logger for a recording one without rebuilding everything.


effect IO {
  fn read(path: str) -> str
}

fn read_thrice() with IO -> str {
  let a = perform IO::read("a.toml")
  let b = perform IO::read("b.toml")
  let c = perform IO::read("c.toml")
  return "{a}|{b}|{c}"
}

// Single outer handler — every read returns "OUTER(...)".

let outer = handle read_thrice() with {
  IO::read(p) => "OUTER({p})",
}
print(outer)

// expected: OUTER(a.toml)|OUTER(b.toml)|OUTER(c.toml)


// An inner handler can be installed mid-flight via a sub-call —

// the same op resolves differently inside the nested `handle`.

fn read_one() with IO -> str {
  return perform IO::read("inner.toml")
}

let mixed = handle read_thrice() with {
  IO::read(p) => {
    let nested = handle read_one() with {
      IO::read(_q) => "NESTED",
    }
    return "{nested}/{p}"
  },
}
print(mixed)
// expected: NESTED/a.toml|NESTED/b.toml|NESTED/c.toml

Un único handle en la capa más externa captura performs de cualquier profundidad de la pila de llamadas. Las funciones intermedias solo necesitan declarar with Efecto — no necesitan saber quién va a manejarlos:

top llama a middle, que llama a leaf; un handler en la cima captura todos los Log::info.

15-transitive-effects.zolo
Playground
// Feature: transitive effects across the call stack — a single

// `handle` block catches `perform` calls from arbitrarily deep

// function calls. Intermediate functions only need to declare the

// effect (`with Log`); they don't need to know who eventually handles

// it.

// When to use: any layered architecture — UI calls service calls

// repository, all of which may log/trace/audit, and you want one

// install at the outermost layer to catch everything.


effect Log {
  fn info(msg: str)
}

fn leaf(n: int) with Log -> int {
  perform Log::info("leaf({n})")
  return n * 2
}

fn middle(n: int) with Log -> int {
  perform Log::info("middle({n})")
  return leaf(n) + 1
}

fn top(n: int) with Log -> int {
  perform Log::info("top({n})")
  return middle(n)
}

// One `handle` at the outermost layer catches every Log::info from

// `top`, `middle`, AND `leaf`. None of those functions had to know

// about each other's handlers.

let result = handle top(3) with {
  Log::info(msg) => print("[log] {msg}"),
}

print("result: {result}")
// expected:

//   [log] top(3)

//   [log] middle(3)

//   [log] leaf(3)

//   result: 7


// Each layer only declared `with Log` — the *protocol*. The

// interpretation lives entirely at the install site, free to swap

// without touching any of the producers.

Desafío

En 05-nested-handlers.zolo, instala un tercer handler aún más interno que devuelva "DEEP". Verifica el orden de resolución.

Consulta también

Buscar en Zolo

9 resultados

enespt-br