Hello,
I've been playing with Nim for a couple of days, and so far I think it is a pretty nice language. I'll still need some time to get comfortable with the syntax (too many years programming in C-like languages), but I think that learning Nim is being a nice experience.
So, here's something I just found. If write code like this:
var i = 1
echo i
var i = 2
echo i
I get an "Error: redefinition of 'i'", just as expected.
Now, I can get around this by rewriting the code as this:
block:
var i = 1
echo i
block:
var i = 2
echo i
This compiles and runs. So far so good. The problem is when I try to encapsulate this block idiom in a template:
template foo(body: stmt) =
block:
body
foo:
var i = 1
echo i
foo:
var i = 2
echo i
Here, again, I get an "Error: redefinition of 'i'", but I'd expect this code to work.
I am doing something wrong here or what?
Thanks a lot!
Thanks for the answer!
{.immediate.} did solve my issue, but I think I didn't understand what exactly it does.
The manual explains that immediate templates parameters "are not checked for semantics before invocation", just as you said. This makes sense for me in the template declareInt example, because the identifier being passed as parameter to the template does not exist at the point of invocation (and thus it would make the semantic checker rightly complain).
But in my example, I cannot see anything semantically ilegal in the parameter I'm passing (which is the code with a variable declaration and a call to echo). In fact, if invoke foo only once, it works. The problem is on the second invocation. It behaves as if non-immediate templates scoping rules were different.
Can you help understand it better, please?
To add to @Jehan's explanation:
I've observed that statements passed into a template as a code block are not (automatically) placed into a new block scope, even though you have indented them in your code in order for them to be recognised as a code block for the template.
Thus, (unless you mark the template as {.immediate.}) any variable declarations inside that code block are initially processed in the global scope, as if they were un-indented global statements, before they are passed to the template.
The following code uses the compile-time 'when' statement and the compile-time 'declaredInScope' proc to demonstrate this:
var iMsg1 = "Compile-time msg #1: i is NOT declared in global scope"
when declaredInScope(i):
iMsg1 = "Compile-time msg #1: i IS declared in global scope"
echo(iMsg1)
var jMsg1 = "Compile-time msg #1: j is NOT declared in global scope"
when declaredInScope(j):
jMsg1 = "Compile-time msg #1: j IS declared in global scope"
echo(jMsg1)
var kMsg1 = "Compile-time msg #1: k is NOT declared in global scope"
when declaredInScope(k):
kMsg1 = "Compile-time msg #1: k IS declared in global scope"
echo(kMsg1)
template foo(body: stmt) =
block:
body
template foomediate(body: stmt) {. immediate .} =
block:
body
var iMsg2 = "Compile-time msg #2: i is NOT declared in global scope"
when declaredInScope(i):
iMsg2 = "Compile-time msg #2: i IS declared in global scope"
echo(iMsg2)
var jMsg2 = "Compile-time msg #2: j is NOT declared in global scope"
when declaredInScope(j):
jMsg2 = "Compile-time msg #2: j IS declared in global scope"
echo(jMsg2)
var kMsg2 = "Compile-time msg #2: k is NOT declared in global scope"
when declaredInScope(k):
kMsg2 = "Compile-time msg #2: k IS declared in global scope"
echo(kMsg2)
foo:
var i = 4
echo i
foomediate:
var j = 5
echo j
block:
var k = 6
echo k
var iMsg3 = "Compile-time msg #3: i is NOT declared in global scope"
when declaredInScope(i):
iMsg3 = "Compile-time msg #3: i IS declared in global scope"
echo(iMsg3)
var jMsg3 = "Compile-time msg #3: j is NOT declared in global scope"
when declaredInScope(j):
jMsg3 = "Compile-time msg #3: j IS declared in global scope"
echo(jMsg3)
var kMsg3 = "Compile-time msg #3: k is NOT declared in global scope"
when declaredInScope(k):
kMsg3 = "Compile-time msg #3: k IS declared in global scope"
echo(kMsg3)
When you run this code, the output is:
Compile-time msg #1: i is NOT declared in global scope
Compile-time msg #1: j is NOT declared in global scope
Compile-time msg #1: k is NOT declared in global scope
Compile-time msg #2: i is NOT declared in global scope
Compile-time msg #2: j is NOT declared in global scope
Compile-time msg #2: k is NOT declared in global scope
4
5
6
Compile-time msg #3: i IS declared in global scope
Compile-time msg #3: j is NOT declared in global scope
Compile-time msg #3: k is NOT declared in global scope
Thanks @Jehan and @jboy for the info!
But this is not the behavior I would expect. In this case, it seems that the need use or not use {.immediate.} depends more on some implementation detail of the compiler than on some well-defined semantics. Either this or there is some bigger truth I couldn't see yet.
(BTW, I am not saying this to blame Nim or anything. I still don't know it enough to praise or blame with confidence :-) Anyway, the experience of learning it has been nice, as I said; lots of interesting stuff!)
Hi @LMB,
As a fellow recent learner of Nim, I've also pondered the ordering of the internal compilation phases. By my count (and there are almost certainly more that I haven't realised), there are 9 distinct-but-interacting processes that occur at compile time (not necessarily in this order!) before you even get to the part where you compile your procs into C functions:
I presume the language developers have decided upon a sensible order in which these processes should occur; but sometimes, you need to move a process forward in the order -- hence, pragmas like {.immediate.}.
There's a bit of documentation available on the compiler architecture, the syntax tree and how the RTL is compiled (and the whole of the documentation page on the compiler internals in general).
Looking through the Nim compiler source, there also seems to be a virtual machine that runs at compile-time, to compute macros & the procs called by macros.
Like you, I would appreciate more detailed information on the ordering & interaction of the compiler phases -- but the code is always there to be read if you really need an answer, and the developers are all already working hard on the implementation of the language itself. :)
Thanx LMB for the question and Jehan for the answer.
But I wonder why the block: statement inside this template
template hider(msg:expr,body:stmt) = echo(msg) block: body
is seemingly elided from instantiations of the hider.
The {.immediate.} pragma DOES solve the problem - but I now wonder what other alterations are performed on the body parameter before insertion into the instantiation.
Here is the rest of the code exhibiting this phenomenon
var x=1 hider("one"): var x=3 echo(x) hider("two"): var x=4 echo(x)
Yeah! Nim is inNimitable !!!
if you declare body as expr instead of stmt it will work too:
template hider(msg:expr,body:expr): stmt =
echo(msg)
block:
body
var x=1
hider("one"):
var x=3
echo(x)
hider("two"):
var x=4
echo(x)
Hmmmm - curious - that worked - using expr instead of stmt.
So if a :stmt template parameter could just as well be a :expr parameter, (I guess because they are both AST) then what are the semantic implications of using stmt ???
Ignore this if I am totally missing the point,
but aren't you doing something fundamentally wrong with your code.
You define a variable once, then use it many times.
So your code should have been
var i = 1
echo i
i = 2
echo i
and if Nim didn't like you having var i declared twice, it was to stop you doing something you shouldn't have??
Thanx to adrianv for answer on stmt semantics.
Usually don't use forums - but this one is nicely focused.
Are thanks considered irritating noise or just basic politeness?
Does this forum have anything like the stackoverflow up voting?