import macros
import typetraits
import strformat
macro fun*(n: varargs[untyped]): typed =
result = newNimNode(nnkStmtList, n)
for x in n:
result.add(newCall("echo", x))
result.add(newCall("echo", newStrLitNode($(n.len))))
# void-return proc
proc bar() = echo "ok"
fun(1+1, "abc", 42) # n.len=3
fun(1+1, "abc") # n.len=2
fun(1+1) # n.len=1
fun() # n.len=0
# gives Error: type mismatch: got <void>
fun(bar()) # n.len=1 ?? why not 0?
in D it works correctly and doesn't break generic code:
void fun(T...)(T n){
static foreach(x:n)
writeln(x);
writeln(T.length);
}
void bar(){writeln("ok");}
fun(1+1, "abc", 42);
fun(1+1, "abc");
fun(1+1);
fun();
fun(bar());
I guess that D has implemented some kind of SFINAE that won't run for args that cause errors? Since Nim has a full macro system, it doesn't need those sorts of things, but you can certainly emulate the behavior. Use the proc compiles, which will return true if an expression compiles without errors:
macro fun*(n: varargs[untyped]): typed =
result = newNimNode(nnkStmtList, n)
var echoedCount = 0
for x in n:
if compiles(echo(x)):
result.add(newCall("echo", x))
echoedCount += 1
result.add(newCall("echo", newStrLitNode($(echoedCount))))
proc bar() = echo "ok"
EDIT: This doesn't work, see below
bar returns nothing, how could you echo it. I see nothing to fix here, especially not the involved lengths.
if nim treated a void-returning function call as being 0 arguments as in D (as if no argument was passed) then there would be no call to echo (ie, fun(bar()) would call bar() and then fun()) ; this introduces less edge cases (no need to consider void variables). Maybe I'm missing some valid use case I haven't thought about?
I guess that D has implemented some kind of SFINAE that won't run for args that cause errors?
no, it's simpler than that: as I was explaining above, in D, variadic argument size will be 0 when calling fun(bar()); since bar returns void
isn't that a better behavior? if not what would be downsides of that?
it doesn't need those sorts of things, but you can certainly emulate the behavior.
your code doesn't work, compiles(echo(x)) still returns true; here's a full modified example so you can click run:
import macros
import typetraits
import strformat
macro fun*(n: varargs[untyped]): typed =
result = newNimNode(nnkStmtList, n)
var echoedCount = 0
for x in n:
if compiles(echo(x)):
result.add(newCall("echo", x))
echoedCount += 1
result.add(newCall("echo", newStrLitNode($(echoedCount))))
proc bar() = echo "ok"
fun(1+1) # n.len=1
fun() # n.len=0
# comment this to make it work
fun(bar())
Oh, right! :) I guess it's testing it on untyped rather than the actual type in the macro. Anyways, this one is definitely working, it just tests for void-typed arguments:
import macros
import typetraits
import strformat
macro fun*(n: varargs[typed]): typed =
result = newNimNode(nnkStmtList, n)
var echoedCount = 0
for x in n:
if x.getTypeInst.typeKind != ntyVoid:
result.add(newCall("echo", x))
echoedCount += 1
result.add(newCall("echo", newStrLitNode($(echoedCount))))
proc bar() = echo "ok"
fun(1+1) # n.len=1
fun() # n.len=0
# comment this to make it work
fun(bar())
isn't that a better behavior? if not what would be downsides of that?
No, I think automatically filtering void arguments in a macro is not a better or intuitive behavior. What if you remove a return type from a function and the compiler just silently starts ignoring it in argument lists instead of producing a type mismatch error? That seems like it could be an interesting source of bugs. And, in general, a compiler ignoring arguments is just not something most programmers would expect. I'm not familiar with any examples of that sort of behavior outside of D/C++, and C++ only uses it in the darkest corners of templates.
I guess what you're asking for is not SFINAE, but it feels similar -- it's a technique used to make template systems have some limited metaprogramming. I think that you'll find that sort of thing is not necessary with Nim, you can just write a macro that does what you want. Is there something specific that you're trying to do that requires the argument-ignoring approach used in D? Maybe we could help you come up with a good solution for it in Nim.
@timothee
Why don't you write D as d? Please learn to write Nim with a capital letter. This is disrespectful at least.
Maybe I'm missing some valid use case I haven't thought about?
Nim macros cannot be directly compared to D templates/generics. You can use Nim macros to implement DSL, such as assembler, JIT engine, shader language, etc:
import macros
proc whatever() = discard
macro my_asm_engine(n: varargs[untyped]): typed =
echo n.treeRepr
my_asm_engine:
mov eax, ebx
sub eax, eax
whatever
If Nim macros choose to ignore void returning function call, it would be unusable to implement DSL at all.
Please push "Run" button, and you'll see my point.
Can D do something like this? I don't think so. (I am not attacking D, it's just a matter of fact)
fun(bar()) # n.len=1 ?? why not 0?
Because fun is a macro, not a function. A macro call is not supposed to "evaluate" its arguments - definitely not untyped ones - before using them. It takes pieces of code, transformed into AST nodes (in this case: one node, hence n.len = 1) and makes a new AST node out of them. Your D example shows a function, which is a completely different animal.
Imagine bar had side effects. Would you really want fun(bar()) to result in code which does nothing (since the varargs argument would be considered "empty")?
@timothee
Why don't you write D as d? Please learn to write Nim with a capital letter. This is disrespectful at least.
I seriously doubt @timothee meant it in a disrespectful way. This is a programming forum, not a parliamentary chambers so no need to call people out for such things.
in D, variadic argument size will be 0 when calling fun(bar()); since bar returns void isn't that a better behavior? if not what would be downsides of that?
It doesn't? Aside from the colon instead of a semi-colon in the for-loop it gives an error:
https://run.dlang.io/is/6sfUA5
"cannot deduce function from argument types !()(void)"
It seems like weird behavior if that were the case. What would fun(1, bar(), 1); do, give a list of 2 integers?
@Dennis my bad I should've verified the D code I posted; with tuples though we have:
import std.stdio:writeln;
void fun(T...)(T n)
{
static foreach (x; n)
writeln(x);
writeln("L=", T.length);
}
void main(){
import std.typecons:tuple;
fun(tuple(1,2).expand); // L=2
fun(tuple(1).expand); // L=1
fun(tuple().expand); // L=0
}
in any case, the previous answers make total sense regarding Nim's behavior.