Hello,
First of all, thanks a lot for Nim ! That's the best python-ish compiled language I found (and trust me, I searched !)
I'm coming from scripted languages, and I'm new to the macros world. But I'm used to metaprogramming thanks to python. From my basic understanding, macros modify the AST.
I found this interesting blog post explaining metaprogramming in nim (https://dlesnoff.github.io/nimProgramming-blog/blogPosts/macroTutorial.html) and wanted to try it on nim 2.2.6
At the beginning, every template example works, but I get no result from every macro example.
For example this one :
macro timesTwo(statements: untyped): untyped =
for s in result:
for node in s:
if node.kind == nnkIntLit:
node.intVal = node.intVal*2
timesTwo:
echo 1 # 2
echo 2 # 4
echo 3 # 6 Which makes kind of sense to me when I see this :
dumpTree:
echo 32
# Outputs this :
StmtList
Command
Ident "echo"
IntLit 32
But nothing prints on my screen, I tried the playground with different versions of nim such as 2.2.0 or 1.6.8 but nothing happens.
It seems I'm missing something here, can somebody please help me ?
macro timesTwo(statements: untyped): untyped =
result = statements # <- result is empty
for s in result:
for node in s:
if node.kind == nnkIntLit:
node.intVal = node.intVal*2Interesting, I did not think someone would read my blog post.
Thanks, I'll fix the example. Sadly, my blog post doesn't cover much and has not been updated for a while.
It misses hygienic variables, bindSym, … Among free teaching materials I would recommend Kiloneie's Youtube channel.
I highly recommend reading Araq's book for examples with idiomatic code.
At the beginning, every template example works, but I get no result from every macro example.
I fixed the timesTwo examples both in the english and french versions. Please tell me if you notice something else wrong. I will surely do a complete pass to fix English writing and add further informations.
Thanks for your quick reply to both of you !
That works for me and I get results @tommo, thanks
Et merci beaucoup @dlesnoff pour la mise à jour du post, je l'ai découvert indirectement grâce à HN. Je vais regarder la suite des exemples sur les macros et te faire un retour au besoin. Ensuite la chaine de Kiloneie dans un premier temps et enfin le livre d'Araq ;)
That's a warm welcome in the nim community :)
There are only 2 little things to correct, everything else is working.
1°) This template produces only 1 "Hello World" and honestly I don't know why :
template doWhile(conditional, loop: untyped) =
loop
while conditional:
loop
var i = 10
doWhile i < 10:
echo "Hello World"
i.inc
2°) The definition of the macro typeMemoryRepr misses an import
import std/strutils # <-- because of capitalizeAscii
macro typeMemoryRepr(typedef: untyped): untyped =
Thanks again, this discussion helped me grasp nim in a better way.
And I'm sure your blog post will help other people too
For number 1 I think you might've meant to do var i = 0, because the expected outcome of a do..while loop is indeed to only run once in the case where i starts out higher than the conditional. That being said there is a little trick that makes the traditional doWhile template better. It's important to remember that templates essentially just copy-pastes code. This means that they can quickly make your code size quite large. Imagine your loop body contained a lot of code, with your doWhile you make two copies of all of it! Furthermore you probably expect things like break or continue to work in a loop. But since the first loop statement is outside the while block they will cause a compilation-error, or even worse break out of an outer loop on accident! The solution is fortunately simple:
template doWhile(conditional, loop: untyped) =
var c = true
while c:
defer: c = conditional
loop
The loop code is now only pasted once, and with the defer statement we are sure the conditional is re-evaluated even in the case you call continue from within your loop.
Meta-programming can be a bit tricky to get right, there are a couple of gotchas around. But when you get it right it's incredibly powerful!
Wrote up a small example showing the difference between the two procs.
Imagine we wanted to only run our code when i was even, and we came up with the (slightly strange) code:
for msg in ["Hello even world", "Goodbye even world"]:
var i = 1
doWhile i < 10:
if i mod 2 != 0:
inc i
continue
echo i, ": ", msg
i += 2
Now, there are better ways of doing this, but if we imagine that our "work" could change i this makes sure we only even run our body when i is even, and skips to the next even number.
With the original template this code throws the error:
Error: invalid control flow: continue This is because the loop statement is copied both inside and outside the while loop, so the first instance has a continue statement that's not in a loop. Not great, but it could be worse. Imagine if we wanted to print more than one message:
for msg in ["Hello even world", "Goodbye even world"]:
var i = 1
doWhile i < 10:
if i mod 2 != 0:
inc i
continue
echo i, ": ", msg
i += 2 Now all of a sudden the code runs, but nothing is printed, what gives?! Well since the first loop is outside our while it is still inside the for loop surrounding our doWhile so the continue statement that was supposed to be in our do..while loop now applies to the for loop instead and skips to the next message instead of the next i! This means that both messages gets skipped over since i is set to one, which is odd, at the beginning of the loop.
With the upgraded doWhile template both cases run fine and produces the expected result.
Thanks!
1) If that was a while procedure, we would not enter the loop as i=10 and we check whether i < 10. Now, with a doWhile, we execute the code once, print, and increase the variable i. Checking the conditional, we have 11 not strictly lower than 10, so we do not execute the code again.