2

I've been playing around with nim for a while, and the thing that trips me up constantly is templates. I want them - at the signature level at least - to work like procs, but they miss certain features (like optional / default parameters - which really threw me a curve ball yesterday)

Anyway, I have what I think is a simple enough pattern for using templates, so here's some code:

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)

That compiles correctly, and works fine. Because my template is dirty, kitty_name is available from within body. Uncomment Ralph and comment out Jimmy, and this works correctly too.

Then, I realise that age does not have a type associated. I certainly don't want that - how silly of me! So I fix it:

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

Suddenly, Jimmy does not compile. Ralph is fine - Ralph uses the template directly, but because Jimmy uses the overridden (if that term even applies to templates?) method, suddenly it's like the main Kitten has closed its borders? It's not dirty enough?

So the question is, why was it working, why is it failing, and is it a bug or a misunderstood feature? Or am I just misusing templates?

(p.s. tried this on 0.17.0 and latest devel branch)

user208769
  • 2,216
  • 1
  • 18
  • 27

2 Answers2

3

I think this is a compiler bug that can be resolved, but I'll provide an explanation for the current behavior.

The key here is that at the call-site of Kitten, the blocks involving the injected kitty_name variable cannot be type checked (due to a reference to a non-existing variable). They must be passed as raw AST and this is indicated by the use of untyped parameter for the template. As soon as you introduce another overload that doesn't use untyped parameter in the same position, the compiler will try to type check the passed-in block during overload resolution and you'll get an error at the call-site (not after template expansion as you seem to be presuming).

The compiler could have used several additional criterias to avoid the error - it could have ruled out one of the overloads on the basis of the number of parameters for example. That's why I think this issue may be resolved in the future.

To work-around the problem, you can rename the two templates, so they don't overload:

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)

In the past, I've also provided a more in-depth explanation for the difference between typed and unyped parameters, which you may also find useful:

typed vs untyped vs expr vs stmt in templates and macros

zah
  • 5,314
  • 1
  • 34
  • 31
2

So, it turns out it isn't a bug.

Added it to the issue tracker, and received the following response from Andreas himself:

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

(The closest manual entry I've found is here nim manual's "Lazy type resolution for untyped" section and the more I read it, the less I think it applies!)

Still trying to get my head around this; I've tried a few simpler examples, and I don't think this is the whole explanation - unless the conclusion is "So mixing untyped and typed causes undefined behaviour - including dropping the dirty pragma" which doesn't fill me with confidence.

One day, I'll delve into the code for this and explain it better. For now, it's an unanswered question.

user208769
  • 2,216
  • 1
  • 18
  • 27