模板的洁净性

默认情况下,模板是 hygienic “洁净”的: 模板内局部声明的标识符,不能在实例化上下文中访问:

  1. template newException* (exceptn: typedesc, message: string): untyped =
  2. var
  3. e: ref exceptn # e 在这里被隐式地 gensym
  4. new(e)
  5. e.msg = message
  6. e
  7. # 允许这样写:
  8. let e = "message"
  9. raise newException(IoError, e)

模板中声明的符号是否向实例所处作用域公开取决于 inject 和 gensym 编译指示。被 gensym 编译指示标记的符号不会公开,而 inject 编译指示则反之。

type , var, let 和 const 等实体符号默认是 gensym,proc,iterator,converter,template,macro 等默认是 inject。 然而,如果实体的名称是由模板参数传入的,那么会标记为 inject。

  1. template withFile(f, fn, mode: untyped, actions: untyped): untyped =
  2. block:
  3. var f: File # 由于 'f' 是一个模板参数,其被隐式注入
  4. ...
  5. withFile(txt, "ttempl3.txt", fmWrite):
  6. txt.writeLine("line 1")
  7. txt.writeLine("line 2")

inject 和 gensym 编译指示是二类注解;它们在模板定义之外没有语义,也不能被再次封装。

  1. {.pragma myInject: inject.}
  2. template t() =
  3. var x {.myInject.}: int # 无法工作

如果不想保持模板的洁净性,我们可以在模板中使用 dirty 编译指示。inject 和 gensym 在 dirty 模板中没有作用。

标记为 gensym 的符号既不能作为 field 用在 x.field 语义中,也不能用于 ObjectConstruction(field: value) 和 namedParameterCall(field = value) 等语义构造。

其原因在于要让以下代码:

  1. type
  2. T = object
  3. f: int
  4. template tmp(x: T) =
  5. let f = 34
  6. echo x.f, T(f: 4)

按预期执行。

但是这意味着 gensym 生成的符号无法用于方法调用语法:

  1. template tmp(x) =
  2. type
  3. T {.gensym.} = int
  4. echo x.T # 不可以,应该使用: 'echo T(x)' 。
  5. tmp(12)