我最近一直在使用模板和宏,但我不得不说我几乎找不到有关这些重要类型的信息。这是我肤浅的理解:
这是一种非常含糊的概念。我想对它们有更好的解释,包括哪些类型应该用作返回。
答案 0 :(得分:11)
这些不同参数类型的目标是在指定编译器应该接受宏作为参数的内容时为您提供几个不断提高的精度。
让我们设想一个可以解决数学方程的假设宏。它会像这样使用:
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树,因为它将接受可在编译时计算的表达式和只能在运行时计算的表达式。
最后,我们可以要求宏接收在编译时提供的特定类型的值。宏可以使用此值来参数化代码生成。这是static parameters的领域。在宏的主体内,它们不再是AST树,而是普通的良好类型值。
到目前为止,我们只讨论过表达式,但Nim的宏也接受并生成块,这是我们可以控制的第二个轴。 expr
通常表示单个表达式,而stmt
表示表达式列表(历史上,它的名称来自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"):
...
指定具体类型与使用expr
具有相同的语义。如果我们使用了默认的stmt
返回类型,编译器就不允许我们传递“表达式列表”,因为if语句显然需要单个表达式。
.immediate.
是历史遗留下来的遗产,当时模板和宏没有参与重载解析。当我们第一次让他们知道类型系统时,大量的代码需要当前的untyped
参数,但是很难重构编译器从一开始就引入它们,而是我们添加了.immediate.
pragma作为强制整个宏/模板的向后兼容行为的一种方法。
使用typed/untyped
,您可以更精细地控制宏的各个参数,.immediate.
pragma将逐步淘汰并弃用。