28

In my Lua script I'm trying to create a function with a variable number of arguments. As far as I know it should work like below, but somehow I get an error with Lua 5.1 on the TI-NSpire (global arg is nil). What am I doing wrong? Thanks!

function equation:init(...)
    self.equation = arg[1]
    self.answers = {}
    self.pipe = {arg[1]}
    self.selected = 1

    -- Loop arguments to add answers.
    for i = 2, #arg do
        table.insert(self.answers, arg[i])
    end
end

instance = equation({"x^2+8=12", -4, 4})
Frog
  • 1,631
  • 2
  • 17
  • 26
  • Please add the code where the `call` metamethod is defined for `equation`. This code does not really make sense without it. – Mankarse Sep 28 '11 at 02:01

3 Answers3

47

Luis's answer is right, if terser than a beginner to the language might hope for. I'll try to elaborate on it a bit, hopefully without creating additional confusion.

Your question is in the context of Lua embedded in a specific model of TI calculator. So there will be details that differ from standalone Lua, but mostly those details will relate to what libraries and functions are made available in your environment. It is unusual (although since Lua is open source, possible) for embedded versions of Lua to differ significantly from the standalone Lua distributed by its authors. (The Lua Binaries is a repository of binaries for many platforms. Lua for Windows is a batteries-included complete distribution for Windows.)

Your sample code has a confounding factor the detail that it needs to interface with a class system provided by the calculator framework. That detail mostly appears as an absence of connection between your equation object and the equation:init() function being called. Since there are techniques that can glue that up, it is just a distraction.

Your question as I understand it boils down to a confusion about how variadic functions (functions with a variable number of arguments) are declared and implemented in Lua. From your comment on Luis's answer, you have been reading the online edition of Programming in Lua (aka PiL). You cited section 5.2. PiL is a good source for background on the language. Unfortunately, variadic functions are one of the features that has been in flux. The edition of the book on line is correct as of Lua version 5.0, but the TI calculator is probably running Lua 5.1.4.

In Lua 5, a variadic function is declared with a parameter list that ends with the symbol ... which stands for the rest of the arguments. In Lua 5.0, the call was implemented with a "magic" local variable named arg which contained a table containing the arguments matching the .... This required that every variadic function create a table when called, which is a source of unnecessary overhead and pressure on the garbage collector. So in Lua 5.1, the implementation was changed: the ... can be used directly in the called function as an alias to the matching arguments, but no table is actually created. Instead, if the count of arguments is needed, you write select("#",...), and if the value of the nth argument is desired you write select(n,...).

A confounding factor in your example comes back to the class system. You want to declare the function equation:init(...). Since this declaration uses the colon syntax, it is equivalent to writing equation.init(self,...). So, when called eventually via the class framework's use of the __call metamethod, the real first argument is named self and the zero or more actual arguments will match the ....

As noted by Amr's comment below, the expression select(n,...) actually returns all the values from the nth argument on, which is particularly useful in this case for constructing self.answers, but also leads to a possible bug in the initialization of self.pipe.

Here is my revised approximation of what you are trying to achieve in your definition of equation:init(), but do note that I don't have one of the TI calculators at hand and this is untested:

function equation:init(...)
    self.equation = select(1, ...)
    self.pipe = { (select(1,...)) }
    self.selected = 1
    self.answers = { select(2,...) }
end

In the revised version shown above I have written {(select(1,...))} to create a table containing exactly one element which is the first argument, and {select(2,...)} to create a table containing all the remaining arguments. While there is a limit to the number of values that can be inserted into a table in that way, that limit is related to the number of return values of a function or the number of parameters that can be passed to a function and so cannot be exceeded by the reference to .... Note that this might not be the case in general, and writing { unpack(t) } can result in not copying all of the array part of t.

A slightly less efficient way to write the function would be to write a loop over the passed arguments, which is the version in my original answer. That would look like the following:

function equation:init(...)
    self.equation = select(1, ...)
    self.pipe = {(select(1,...))}
    self.selected = 1

    -- Loop arguments to add answers.
    local t = {}
    for i = 2, select("#",...) do
        t[#t+1] = select(i,...)
    end
    self.answers = t
end
Community
  • 1
  • 1
RBerteig
  • 41,948
  • 7
  • 88
  • 128
  • 1
    Wow, thanks a lot for the detailed answer! This is indeed exactly what I meant. I expected `...` to create a table called `arg`, which didn't happen. Thanks again for all your help! – Frog Oct 03 '11 at 13:05
  • 2
    A correction - select(n, ...) does not return the n'th argument: rather, it returns the n'th argument as well as all subsequent arguments. – Amr Bekhit Jun 25 '13 at 14:38
  • 2
    @AmrBekhit, right, a detail that is easily overlooked. `select(n,...)` returns the nth and following argument, which is often narrowed by context to just the nth. However, that property is useful here, so I will update my answer. – RBerteig Jun 25 '13 at 18:01
  • @RBerteig Ah yes, I see what you mean now. Even though `select(n, ...) `will possibly return more than one item, the expression `a = select(n, ...)` will only assign the first item to `a` and discard the rest. – Amr Bekhit Jul 02 '13 at 08:42
  • 4
    I spent the last hour and a half trying to figure out why examples copied straight out of the book didn't work and `arg` was always `nil` or garbage data. Big thumbs-up for explaining that the `...` syntax fundamentally changed behavior between Lua 5.0 and 5.1! – Sean Werkema May 03 '18 at 19:44
32

Try

function equation:init(...)
    local arg={...}
    --- original code here
end
lhf
  • 70,581
  • 9
  • 108
  • 149
  • 10
    That solves the problem. But why doesn't it work as advocated on the internet? – Frog Sep 27 '11 at 19:21
  • @Frog: How are you calling `equation.init`? All you seem to be calling is `equation`. Is there some class system in place that you are using? – Nicol Bolas Sep 27 '11 at 20:31
  • @Frog, Please update the question with a pointer to where you got advice, or ask a new question. The internet is a big place.... and not all of it has reliable advice. ;-) – RBerteig Sep 27 '11 at 22:58
  • @NicolBolas: Sorry for the late response! Indeed, there is a class system available. You can find information about it [here](http://wiki.inspired-lua.org/class()). Does this cause it not to function as it should? – Frog Sep 29 '11 at 20:22
  • @RBerteig: What should I change to the question? – Frog Sep 29 '11 at 20:22
  • 1
    @Frog, "why doesn't it work as... on the internet?" is a pretty open ended question. If there is a specific source of the advice you were following, it might be helpful to edit the question to include a link to it. That way, you have provided some context to your problem, and the denizens of SO will be able to be more specific. – RBerteig Oct 01 '11 at 07:37
  • 1
    @RBerteig: I agree, but the problem is that I haven't got and found an answer to that question myself. You see, [here](http://www.lua.org/pil/5.2.html) they explain that you can use `...` to create an array of arguments called `arg`, while I have to do `local arg = {...}`. I still don't understand why I have to do that... If I find out why, I will of course post it here! – Frog Oct 01 '11 at 12:42
  • @lhf Thank you very much! This works for me with [Minetest's](http://minetest.net/) Lua implementation. – AntumDeluge May 09 '21 at 22:58
  • I came here confused for the same reason lol @Frog – Josh Yolles Jun 28 '22 at 03:48
  • "On the Internet" reads more like an ironic expression to me! In either case, Lua authors definitely need to update documentation. Regarding the answer: using `arg[i]` appears less verbose than the accepted answer which suggests to use `select(i,...)`, but perhaps it's a matter of taste, especially when performance is not a big concern. – Xrayez Apr 24 '23 at 19:13
0

To add to RBerteig's great explanation (tldr: breaking changes between Lua 5.0 and 5.1), here's a cleaner use of select.

self.equation = arg[1] becomes self.equation = ... because in Lua only the first argument will be assigned and the rest dismissed (no matter how many there are).

self.pipe = {arg[1]} becomes self.pipe = { (...) }, similar to above, the brackets ensure only one single argument is evaluated and the result given to the "outside" of brackets. As a result this table will only contain the first argument.

It is recommended to use select like in RBerteig's code: self.answers = { select(2,...) } because it will return all arguments from 2 until the end in a single call.

Avoid using select in a loop and only picking a single value per iteration, because it ends up being O(n²) in complexity. Why? select(a, ...) (where n is the total count) returns n-a+1 values on each call and by processing one value at a time you would need n select calls. select returns all values from a to n and they are only dismissed during assignment.

PS: Lua 5.2 introduced table.pack(...) that can be used to create tables in this scenario.

vadcx
  • 1