Templates

27.1 What Templates Are

Hygienic, AST-level substitutions. They're like inline macros without AST manipulation code. They're expanded at the call site.

template `!=`(a, b: untyped): untyped = not (a == b)   # how the stdlib defines it

template withLock(l: Lock, body: untyped) =
  acquire(l)
  try:
    body
  finally:
    release(l)

withLock myLock:
  counter += 1

27.2 untyped vs typed Parameters

untyped: the argument isn't type-checked before substitution (allows new identifiers and arbitrary code blocks).

typed: the argument is semantically checked first.

template declareInt(name: untyped, val: int) =
  var name = val
declareInt(answer, 42)
echo answer

template measure(label: string, body: untyped) =
  let t0 = cpuTime()
  body
  echo label, ": ", cpuTime() - t0, "s"
measure "sorting":
  data.sort()

27.3 Hygiene, inject, gensym, dirty

template withFile(f, filename: untyped, mode: FileMode, body: untyped) =
  let f = open(filename, mode)     # `f` is injected because it's a parameter name
  try: body
  finally: f.close()

template defineHelper() =
  var helperVar {.inject.} = 1     # visible to the caller
  var hidden = 2                   # gensym'd: not visible

template unsafeTpl() {.dirty.} =   # no hygiene at all
  echo x

27.4 Lazy Evaluation

template debugLog(msg: string) =
  when not defined(release):
    echo msg                       # `msg` is evaluated only when the branch is compiled in

template `??`(a, b: untyped): untyped =
  let x = a
  if x.isSome: x.get else: b       # b is evaluated lazily