我最近一直在使用模板和宏,但我不得不说我几乎没有找到关于这些重要类型的信息。这是我肤浅的理解:
- typed/expr 是以前必须存在的东西,但您可以使用 .immediate。克服它们。
- untyped/stmt 是之前没有定义的东西/一个或多个语句。
这是一个非常模糊的类型概念。我想对它们有更好的解释,包括哪些类型应该用作返回。
这些不同参数类型的目标是在指定编译器应该接受什么作为宏的参数时为您提供几个不断增加的精度级别。
让我们想象一个可以解决数学方程的假设宏。它将像这样使用:
solve(x + 10 = 25) # figures out that the correct value for x is 15
在这里,宏只关心提供的 AST 树的结构。它不要求同一棵树是当前范围内的有效表达式(即已x
定义等)。该宏只是利用了 Nim 解析器,该解析器已经可以解码大多数数学方程,将它们变成更容易处理的 AST 树。这就是untyped
参数的用途。他们没有得到语义检查,你得到了原始的 AST。
精度阶梯的下一步是typed
参数。它们允许我们编写可以接受任何表达式的通用类型的宏,只要它在当前范围内具有适当的含义(即可以确定其类型)。除了更早地捕获错误之外,这还有一个优点是我们现在可以使用宏体内的表达式类型(使用macros.getType
proc)。
通过要求特定类型的表达式(具体类型或类型类/概念),我们可以得到更精确的结果。宏现在将能够像常规 proc 一样参与重载决议。重要的是要了解宏仍然会接收 AST 树,因为它会接受可以在编译时评估的表达式和只能在运行时评估的表达式。
最后,我们可以要求宏接收在编译时提供的特定类型的值。宏可以使用此值来参数化代码生成。这是静态参数的领域。在宏的主体内,它们不再是 AST 树,而是普通的类型良好的值。
到目前为止,我们只讨论了表达式,但 Nim 的宏也接受和生成块,这是我们可以控制的第二个轴。expr
通常表示单个表达式,whilestmt
表示表达式列表(历史上,它的名称来自 StatementList,在 Nim 中统一表达式和语句之前,它作为一个单独的概念存在)。
使用模板的返回类型最容易说明区别。考虑newException
系统模块中的模板:
template newException*(exceptn: typedesc, message: string): expr =
## creates an exception object of type ``exceptn`` and sets its ``msg`` field
## to `message`. Returns the new exception object.
var
e: ref exceptn
new(e)
e.msg = message
e
即使认为构造异常需要几个步骤,通过指定expr
模板的返回类型,我们告诉编译器只有最后一个表达式将被视为模板的返回值。其余的语句将被内联,但巧妙地隐藏在调用代码之外。
作为另一个例子,让我们定义一个特殊的赋值运算符,它可以模拟 C/C++ 的语义,允许在 if 语句中进行赋值:
template `:=` (a: untyped, b: typed): bool =
var a = b
a != nil
if f := open("foo"):
...
指定具体类型与 using 具有相同的语义expr
。如果我们改用默认stmt
返回类型,编译器将不允许我们传递“表达式列表”,因为 if 语句显然需要一个表达式。
.immediate.
是很久以前的遗产,当时模板和宏不参与重载决议。当我们第一次让他们了解类型系统时,大量代码需要当前untyped
参数,但是重构编译器以从一开始就引入它们太难了,相反我们添加了.immediate.
pragma 作为强制向后兼容行为的一种方式对于整个宏/模板。
使用typed/untyped
,您可以更精细地控制宏的各个参数,并且.immediate.
pragma 将逐渐被淘汰和弃用。