proc dispayVarargs(va: varargs[int]) =
echo va.len
var se: seq[int] = @[2, 3, 4]
dispayVarargs(se)
I am happy that it works but it feels like breaking design.Besides the ability to apply function (like stringify $) to the parameters, varargs is nothing but an openArray with an alternative way of specifying arguments available. The normal way is still useful for e.g. when you forward parameters or when you don't know how many parameters you have and you pas over a seq:
import strutils
proc printStuff(x: varargs[string]) =
for element in x:
echo element
let lines = readFile("file.txt").splitLines()
# the same proc used in two occaisons
# afaik in C++ you would have to write a wrapper for printStuff
printStuff "Hi, how are you going?", "Here's the content of a file"
printStuff lines
The surprise (as I understand it, and I remember it surprised me too reading the docs) is the automatic expansion of the arguments. The consistent way would be for printStuff(lines) above to provide 1 argument of type seq[string], in the same way each arg of printStuff(lines, lines) would do. As it is, a single "lines" argument results in an arbitrary length x, but two arguments result in x.len==2.
e.g. in Python, you have the same functionality, but you have to prefix the arg with a * to say "I wish to expand this list into an argument list and not pass it as a single argument", which is consistent with the use of * in similar contexts, and with the passing of any other individual argument.
There is no reason to use a special syntax to tell the compiler that expansion is needed. Thanks to static typing, the compiler is able to see if the array or sequence must be transmitted as a whole to the procedure or must be expanded.
In Python, you need a special syntax. Given a function def f(*x) and a list lst, if you write f(lst), x will be a tuple whose first (and only) elements will be lst. You have to use the special syntax f(*x) to transmit each element of lst as an individual argument and, in this case, x will contain the elements of lst and not the whole list.
There is another case where the Nim compiler uses its knowledge of types to help the programmer: this is the automatic dereferencing of references and pointers. Given a ref object, when accessing a field a via a reference p, you write p.a and the compiler knows that it must dereference p. In C, you would have to write something like (*p).a or p->a.
There is another case where the Nim compiler uses its knowledge of types to help the programmer: this is the automatic dereferencing of references and pointers.
No, it's just a stylistic decision, one that some people find surprising as it is a special case.
Python could have just as well automatically expanded a list into varargs - in fact, it has more information at the time of the call than Nim does, being dynamic.
If you want to pass just one list argument to a vararg Nim proc, you need to call e.g. printStuff(@[lines]), because printStuff(lines) would ""unwrap"" it and provide a list of unknown length; whereas either printStuff(lines,lines) or printStuff(@[lines,lines]) would result in an argument list of 2 arguments, regardless of the length of lines.
This (apparent) inconsistency is the source of the confusion. Personally, I prefer the Python behaviour, but since we're past v1.0, I don't think it's worth discussing a breaking change even if everyone agrees with me (And not everyone does).
As regards implicit dereferencing, the compiler, when looking for the field "a" has to detect that "p" is a reference or a pointer, check if the referenced type is an object or a tuple and that it contains a field "a" and if this is OK generate the code for dereferencing. I don’t see that as stylistic. Other languages will detect an error as "p" has not the right type.
As regards the varargs, that Python has more information at runtime than Nim, I agree. But, the expansion has to be done at compile time, not at runtime and Python lacks information about the expected argument type. When you write def f(x):, you don’t give the compiler a clue about the type of the elements of "x" which may be a list, an int, an dict and so on. So, if I call it with a list (without the "*"), the compiler cannot do the expansion as it doesn’t know what is expected (and to complicate things, contrary to Nim, the varargs need not to be of the same type).
And your example with printStuff(@[lines] is wrong. If you want to pass a list of strings as a whole to a varargs, you have to declare the procedure to accept a sequence of strings, not a string. "printStuff" has been declared to accept a varargs[string] not a varargs[seq[string]], so, when writing printStuff(lines) there is no way you can get a unique argument "lines" as it has not the expected type "string".
But this works:
proc printStuff(x: varargs[seq[string]]) =
for element in x:
echo element
let lines = @["abc", "def"]
printStuff(lines)
Comparing Nim and Python is frequently a bad idea as the languages are fundamentally different. Python has done things a way which is logical for a dynamically typed language. And Nim does the things another way which is consistent with its nature.
Varargs has to be the last parameter, openarray(s) can be anywhere.
Actually they also can be anywhere.
Caveat: varargs[untyped] in macros (specifically not varargs[typed] and not in template) have a bug and are currently only usable as the last parameter.
I have tried to declare a varargs[string | seq[string]] but the compiler rejects it. It seems that for varargs, more strict rules apply to avoid some nasty problems.
But there may exist other problematic cases as Araq said. Adding – in a future version – an explicit unpacking as he suggests would simplify things (and Python people would say that explicit is better than implicit). Personally, I would not care as I have never used this feature, but unpackVarargs is really an ugly name :-).