lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Hi! I hope this is the right place for this suggestion. I have been writing a C library for interfacing with another another language, and I have came by a case where being able to determine the number of return values the caller of a particular function expects might be advantageous.

Let's say the library can produce a Lua function for every external function from the foreign language (based on metadata or other information, that does not matter). Suppose there is an external function "string SetString(string)" that changes some variable and returns the original value.
In the foreign language, returning string is simply returning one pointer, but if I wanted to adapt this function to Lua using my library, I would have to write this code:

local SetString = getfunc("SetString")
SetString("new value")

In this case, the caller wants 0 return values from the function (this information is known at runtime via L->ci->nresults), but the called function has no access to this number. It must always marshal the string in some way and return it, even if the runtime scraps the string immediately afterwards.

I find this occasion suitable for a new API function, lua_numresults, which shall return the number of expected return values. In this case, a non-zero check is all that is needed to optimize the function and prevent unneeded construction of objects.

After asking (https://stackoverflow.com/questions/51931884) about this function, I got another example of a function that returns a number of information about a matrix, and could need to compute both eigenvalues or eigenvectors, or only part of them, depending on the number of results actually needed. One computation can be omitted if its result is not used, decreasing the number of required operations.

I find this solution better than passing another parameter to specify the number of results needed, for three reason. It clutters the parameters of a function with a value that can be provided implicitly, makes it harder to use functional operations like composition, currying etc., and can cause bugs like this one, by having a single information specified in two places:

local old = SetString(0, "new value") -- old is always nil

Another advantage, other than optimization, is debugging. Being able to see if the values of the function will ever be used is useful for functions without any side effects, which could produce a warning if the return value is never used, like in this case:

local str = "apples"
string.gsub(str, "apple", "orange")

It might not be immediately obvious to beginners that is call is nonsensical, and even advanced programmers could sometimes forget that the original string cannot be modified. This case can be caught by the callee, and in some "debug" mode, producing a warning could help a lot.

I hope I have presented the usefulness of this feature, so to complement it, I suggest a number of additional features:

take(num, func, ...) in the base package, which calls a function "func", passing along the arguments, and returns at most "num" results (the rest shall not be filled with nils), and if numresults is smaller than "num", it shall be used instead (tail call return optimization).

debug.numresults(level) or a similar to retrieve the number of results from a particular call site

#define lua_atmost(n) (LUA_MULTRET - (n))
With lua_numresults added, functions like lua_call & co. are incomplete, since there is no way to both specify the upper limit on the number of return values, and determine the actual number returned, since lua_call fills the rest with nils. A call like this:
lua_call(L, 0, lua_atmost(3), 0)
shall push on the stack 0, 1, 2 or 3 three values, depending on the number of values actually returned by the called function (passing only 3 will always push exactly 3 values). However, in the actual called function, lua_numresults shall always return a nonnegative number or LUA_MULTRET, since lua_atmost only controls how the returned values are processed in lua_call, after the actual function is called.

Tail call return optimization - "function f(...) return g(...) end" should make lua_numresults return 2 in g on `local a, b = f()`, i.e. the number should be copied from the call site of the external function to the call site of the internal function (seems to be LUA_MULTRET at the moment).

lua_numresults and take can be implemented quite easily, and support for lua_atmost needs modifications in the respective functions to acknowledge the new values.

Of course, other features like return value concatenation ("f(), g()" takes exactly 1 return from f) would be welcome as well to fit these features, but I have not decided on them yet.

Other suggestions, thoughts and ideas are welcome.
Thanks!