Saltar al contenido

Composición y Decorators Propios

En esta página

Varios decorators pueden apilarse antes de la misma función o struct. El orden de aplicación es de abajo hacia arriba: el decorator más cercano al objetivo se aplica primero. El ejemplo siguiente combina caché, rastreo y retry en una sola función, y también muestra un struct con @derive + @serialize:

@retry + @log + @memoize en función; @serialize + @derive en struct; @log + @benchmark juntos.

12-stacked.zolo
Playground
// Feature: Stacked decorators — composition

// Syntax: several `@a` `@b` lines before the `fn`/`struct`. Application

// order is bottom to top (the one closest to the target applies first).

// When to use: combine capabilities without entangling logic in the body.


// Case 1: cache + tracing + retry — a "good citizen" function.

@retry(3)
@log
@memoize
fn lookup_user(id: int) -> str {
  // In production: a DB/HTTP call. Here, a deterministic stub.

  return "user-{id}"
}

print(lookup_user(1))
print(lookup_user(1))  // memoize avoids re-execution

print(lookup_user(2))

// expected:

//   user-1

//   user-1

//   user-2


// Case 2: struct with Debug + Clone + Eq + serialize.

@serialize
@derive(Debug, Clone, Eq)
struct Item {
  sku: str,
  price: float,
}

let a = Item { sku: "X-1", price: 9.9 }
let b = a.clone()
print(a)
// expected: Item { sku: "X-1", price: 9.9 }

print("a == b? {a == b}")
// expected: a == b? true

print(a.to_json())

// expected: {"sku":"X-1","price":9.9}


// Case 3: log + benchmark — instrument and measure together.

@log
@benchmark
fn hot_path(n: int) -> int {
  return n * n + n
}

print("hot(10) = {hot_path(10)}")
// expected: hot(10) = 110

El calificador @const en un parámetro exige que el argumento correspondiente sea una constante en tiempo de compilación — un literal, una referencia a const o una expresión aritmética sobre valores comptime. Esto habilita validaciones (mediante const_assert) que se ejecutan en la compilación, no en el runtime:

Literal, referencia a const y aritmética comptime son aceptados; una variable de runtime es rechazada.

15-const-param.zolo
Playground
// Feature: `@const` parameter qualifier

// Syntax: `fn name(@const NAME: TYPE, ...)`

// When to use: function runs at runtime, but a specific argument must

//   be a comptime-constant — useful for fixed-size buffers, format

//   string validation, SQL DSL validation, bit-field accessors, and

//   any API where the argument enables compile-time checks via

//   `const_assert`.

//

// See `specs/const-parameter.md` for the full specification.


// 1. Basic shape ----------------------------------------------------

fn ring_buffer(@const capacity: int, name: str) -> int {
  // (Inside the body the parameter behaves like a runtime value;

  //  comptime-conscious validation runs at the call site.)

  return capacity
}

// 2. Accepted call shapes ------------------------------------------

let r1 = ring_buffer(64, "audio")            // literal — OK

print("r1 = {r1}")

const DEFAULT_CAP = 128
let r2 = ring_buffer(DEFAULT_CAP, "video")   // const reference — OK

print("r2 = {r2}")

let r3 = ring_buffer(32 + 16, "control")     // arithmetic over comptime values — OK

print("r3 = {r3}")

// 3. Rejected call shape -------------------------------------------

// Uncomment to trigger the type error:

//

//   let n = read_int()

//   let bad = ring_buffer(n, "dynamic")

//   //                   ^ E_ConstArgNotComptime: argument 1 must be a

//   //                     comptime-constant expression (parameter is

//   //                     declared `@const`)

//

// Bypass options:

//   - hoist the value into a `const`:

//       const N = 256

//       ring_buffer(N, "fixed")

//   - inline the literal directly:

//       ring_buffer(256, "fixed")


// 4. Why @const is useful ------------------------------------------

// Inside `ring_buffer`, the `capacity` parameter can be referenced

// in a `const_assert` to enforce invariants at every call site:

//

//   fn ring_buffer(@const capacity: int) {

//     const_assert capacity > 0

//     const_assert capacity.is_power_of_two()

//     ...

//   }

//

// (Comptime evaluation of `@const` params in `const_assert` is a

// follow-up — the parameter is comptime-known at the call site, but

// the check passes through runtime today. Track in

// `specs/const-parameter.md` §Interactions.)

@mixin permite crear decorators en Zolo puro. Dentro del cuerpo, super() llama a la función objetivo y devuelve su valor. El mixin decide qué hacer con ese valor: reenviarlo, transformarlo, ignorarlo (short-circuit) o llamarlo múltiples veces. Los mixins también aceptan parámetros y funcionan en métodos de impl:

Around, transformación, composición bottom-up, short-circuit, parámetro y mixin en método.

16-mixin.zolo
Playground
// Feature: Mixin functions — user-defined decorators written in Zolo.

//

// A `@mixin fn` wraps a target function. Inside the body, `super()` calls the

// wrapped target. You apply a mixin like any decorator: `@name`. Unlike the

// built-in decorators (@memoize, @retry, ...), mixins live in userland — the

// compiler only knows that a mixin is "a fn whose body may call super()".

//

// Mixins compose bottom-up, exactly like stacked decorators: the one closest

// to the `fn` is the innermost layer.


// ── 1. Around: run code before and after the target ──────────────────

// `super()` returns whatever the target returns; the mixin's own return

// value is what the caller sees (here we just pass it through).

@mixin
fn traced() -> int {
  print(">> enter")
  let r = super()
  print("<< exit")
  return r
}

@traced
fn square(n: int) -> int {
  return n * n
}

print(square(5))
// expected:

//   >> enter

//   << exit

//   25


// ── 2. Transform the result ──────────────────────────────────────────

// The mixin can do anything with the wrapped call's value.

@mixin
fn doubled() -> int {
  return super() * 2
}

@doubled
fn ten() -> int {
  return 10
}

print(ten())
// expected: 20


// ── 3. Composition (stacking) ────────────────────────────────────────

// Bottom-up: `plus_one` is innermost (10 + 1 = 11), `times_three` wraps it

// (11 * 3 = 33). Reversing the two lines would yield (10 * 3) + 1 = 31.

@mixin
fn plus_one() -> int {
  return super() + 1
}

@mixin
fn times_three() -> int {
  return super() * 3
}

@times_three
@plus_one
fn base() -> int {
  return 10
}

print(base())
// expected: 33


// ── 4. Short-circuit: a mixin may choose NOT to call super() ──────────

// Useful for feature flags, circuit breakers, dry-runs. The target never

// runs, so its body is skipped entirely.

@mixin
fn disabled() -> int {
  print("blocked")
  return 0
}

@disabled
fn dangerous() -> int {
  print("this should never run")
  return 999
}

print(dangerous())
// expected:

//   blocked

//   0


// ── 5. Parameterised mixins ──────────────────────────────────────────

// Declare parameters on `@mixin(...)`; pass values at the application site.

// This is the shape of @retry(n), @cache(ttl), @rate_limit(rps), etc. The

// parameter is an ordinary local inside the body; super() still forwards the

// target's own arguments.

@mixin(factor: int)
fn scaled() -> int {
  return super() * factor
}

@scaled(4)
fn six() -> int {
  return 6
}

print(six())
// expected: 24


// A parameter can also drive how many times the target runs.

@mixin(times: int)
fn repeated() -> int {
  var total = 0
  for _ in 0..times {
    total = total + super()
  }
  return total
}

@repeated(3)
fn unit() -> int {
  return 1
}

print(unit())
// expected: 3


// ── 6. Mixins on methods ─────────────────────────────────────────────

// super() forwards the receiver automatically. A mixin that reads `self`

// becomes a method mixin and binds the receiver in its body.

@mixin
fn audited() -> int {
  print("audit: balance was {self.balance}")
  return super()
}

struct Account {
  balance: int,
}

impl Account {
  @audited
  fn read(self) -> int {
    return self.balance
  }
}

let acct = Account { balance: 42 }
print(acct.read())
// expected:

//   audit: balance was 42

//   42

Desafío

Crea un @mixin llamado clamped(min: int, max: int) que restrinja el valor de retorno de la función al intervalo [min, max]. Aplícalo a una función que pueda devolver valores fuera de ese intervalo y confirma el resultado.

Buscar en Zolo

9 resultados

enespt-br