带有类型化属性的脏模板

Dirty templates with typed attributes

我使用 nim 已经有一段时间了,但经常让我感到困惑的是模板。我希望它们 - 至少在签名级别 - 像 procs 一样工作,但它们缺少某些功能(例如可选/默认参数 - 昨天真的让我感到困惑)

无论如何,我有我认为使用模板的足够简单的模式,所以这里有一些代码:

template Kitten*(name: string, age, body: untyped) {.dirty.} =
  var kitty_name = name  # my {.dirty.} exposes this
  echo("Hi, " & name)
  if age != 0: echo("wow, " & $age & " years old already!")
  body
  echo("Bye, " & name)

template Kitten*(name: string, body: untyped) {.dirty.} =
  Kitten(name, 0, body)


Kitten("Jimmy"):
  echo("It's really nice to meet you, " & kitty_name)
## Ralph and Jimmy cannot co-exist - it's fine, I understand the issue here
# Kitten("Ralph", 5):
#   echo("Great that you joined us, " & kitty_name)

编译正确,运行良好。因为我的模板是脏的,所以 kitty_name 可以从 body 中获得。取消注释 Ralph 并注释掉 Jimmy,这也能正常工作。

然后,我意识到 age 没有关联的类型。我当然不想那样——我真傻!所以我修复它:

template Kitten*(name: string, age:int, body: untyped) {.dirty.}=

突然,Jimmy 无法编译。 Ralph 很好 - Ralph 直接使用模板,但是因为 Jimmy 使用覆盖(如果该术语甚至适用于模板?)方法,突然就像主要的 Kitten 关闭了它的边界?还不够脏?

所以问题是,它为什么有效,为什么失败,是错误还是被误解的功能?还是我只是误用了模板?

(p.s。在 0.17.0 和最新的开发分支上试过这个)

所以,事实证明这不是一个错误。

将其添加到问题跟踪器中,并收到了 Andreas 本人的以下回复:

Overloads of templates need to agree on the positions of the untyped parameters. This is documented in the manual.

(我找到的最接近的手册条目在这里 nim manual's "Lazy type resolution for untyped" section,我读得越多,我就越不认为它适用!)

仍在努力解决这个问题;我已经尝试了一些更简单的示例,但我认为这不是全部解释 - 除非结论是 "So mixing untyped and typed causes undefined behaviour - including dropping the dirty pragma" 这并没有让我充满信心。

总有一天,我会深入研究代码并更好地解释它。目前,这是一个悬而未决的问题。

我认为这是一个可以解决的编译器错误,但我会提供对当前行为的解释。

这里的关键是在 Kitten 的调用点,涉及注入的 kitty_name 变量的块无法进行类型检查(由于引用了不存在的变量)。它们必须作为原始 AST 传递,这通过使用模板的 untyped 参数来指示。一旦您引入另一个不在同一位置使用 untyped 参数的重载,编译器将尝试在重载解析期间对传入的块进行类型检查,您将在调用站点收到错误(不是像您假设的那样在模板扩展之后)。

编译器本可以使用几个额外的标准来避免错误 - 例如,它可以根据参数的数量排除其中一个重载。这就是为什么我认为这个问题将来可能会得到解决。

要解决此问题,您可以重命名这两个模板,以免它们超载:

template AgedKitten*(name: string, age: int, body: untyped) {.dirty.} =
  block:
    var kitty_name = name  # my {.dirty.} exposes this
    echo("Hi, " & name)
    if age != 0: echo("wow, " & $age & " years old already!")
    body
    echo("Bye, " & name)

template Kitten*(name: string, body: untyped) {.dirty.} =
  AgedKitten(name, 0, body)

Kitten("Jimmy"):
  echo("It's really nice to meet you, " & kitty_name)

AgedKitten("Ralph", 5):
  echo("Great that you joined us, " & kitty_name)

过去,我还对类型化参数和非类型化参数之间的区别进行了更深入的解释,您可能也会觉得有用: