Pular para o conteúdo

Higiene, Recursão e Argumentos de Bloco

Nesta página

Higiene

Variáveis criadas com let dentro do corpo de um macro recebem um sufixo único na expansão. Isso significa que um let result dentro do macro nunca sobreescreve um result que já existe no escopo do chamador. Você pode reusar nomes óbvios como result, tmp e i sem medo de colisão:

twice! e inc_tmp!/dec_tmp! demonstram que cada expansão isola seus próprios bindings.

05-hygiene.zolo
Playground
// Feature: Macro hygiene — internal vars do not leak to the caller

// Syntax: variables declared with `let` inside the body get a unique

// suffix on expansion, avoiding collisions with external names.

// When to use: you can reuse "obvious" names like `result`, `tmp`, `i`

// inside macros without fear of overwriting the caller's bindings.


// The macro declares `result` internally.

macro twice(x) {
  let result = $x * 2
  print("inside the macro: {result}")
}


// The caller also has `result` — it is not overwritten.

let result = 100
twice!(7)
print("outside: {result}")

// expected:

// inside the macro: 14

// outside: 100


// The same `tmp` name in three different macros — no collision.

macro inc_tmp(x) {
  let tmp = $x + 1
  print("inc: {tmp}")
}


macro dec_tmp(x) {
  let tmp = $x - 1
  print("dec: {tmp}")
}


let tmp = 999
inc_tmp!(10)
dec_tmp!(10)
print("original tmp: {tmp}")
// expected:

// inc: 11

// dec: 9

// original tmp: 999

Recursão

Um macro pode invocar outro macro no seu corpo. A expansão é resolvida recursivamente (limite de ~64 níveis). Isso permite compor regras pequenas em vez de duplicar lógica — min3! reusa min2!, e min4! reusa ambos:

Cadeia min2!min3!min4! e composição com double!(inc!(5)).

04-recursive.zolo
Playground
// Feature: Recursive macros — one macro calling another

// Syntax: the `outer` macro invokes `inner!(...)` in its body

// When to use: compose small macros to avoid duplicated code, modular

// textual rules (depth limit ~64).


// Basic block: minimum of two.

macro min2(a, b) {
  if $a < $b { $a } else { $b }
}


// Reuses min2 to build min3.

macro min3(a, b, c) {
  min2!($a, min2!($b, $c))
}


// And min4 reuses min3 + min2.

macro min4(a, b, c, d) {
  min2!(min2!($a, $b), min2!($c, $d))
}


print(min2!(7, 3))

// expected: 3


print(min3!(7, 2, 5))

// expected: 2


print(min4!(9, 4, 6, 1))

// expected: 1


// Composition also works with different expressions at each level.

macro double(x) {
  $x + $x
}


macro inc(x) {
  $x + 1
}


print(double!(inc!(5)))
// expected: 12   ((5 + 1) + (5 + 1))

Blocos como argumento

Qualquer parâmetro pode receber um bloco { ... }. Na expansão, o bloco é colado textualmente onde $param aparece, criando construções de controle personalizadas. repeat_n! implementa um laço; logged! envolve o corpo com mensagens de início e fim:

repeat_n!(n, { ... }) e logged!(label, { ... }) como construtores de controle de fluxo.

06-block-arg.zolo
Playground
// Feature: Macros that receive a block as an argument

// Syntax: `name!(arg, { ...statements... })` — any arg can be a block

// inside `{}`, expanded textually wherever `$arg` appears.

// When to use: build custom "control-flow constructs" — retry, timed,

// with_lock, etc. — where the user passes the "body".


// Repeats a block N times, counting attempts.

macro repeat_n(times, body) {
  var __i = 0
  while __i < $times {
    __i = __i + 1
    $body
  }
}


repeat_n!(3, {
  print("tick")
})

// expected:

// tick

// tick

// tick


// Macro that wraps the body with try-style logging.

macro logged(label, body) {
  print("[start] {$label}")
  $body
  print("[done] {$label}")
}


logged!("important work", {
  let x = 10 + 20
  print("computed {x}")
})
// expected:

// [start] important work

// computed 30

// [done] important work

Desafio

Crie um macro timed!(label, body) que imprime "[start] label", executa body, e depois imprime "[end] label". Use-o para envolver dois blocos diferentes e verifique que as mensagens aparecem corretamente intercaladas.

Buscar no Zolo

9 resultados

enespt-br