Lua Variadic Arguments Advanced Usage


Example

As stated in the basic examples, you can have variable bound arguments and the variable argument list (...). You can use this fact to recursively pull apart a list as you would in other languages (like Haskell). Below is an implementation of foldr() that takes advantage of that. Each recursive call binds the head of the vararg list to x, and passes the rest of the list to a recursive call. This destructures the list until there is only one argument (select('#', ...) == 0). After that, each value is applied to the function argument f with the previously computed result.

function foldr(f, ...)
    if select('#', ...) < 2 then return ... end
    local function helper(x, ...)
        if select('#', ...) == 0 then
          return x
        end
        return f(x, helper(...))
    end
    return helper(...)
end

function sum(a, b)
    return a + b
end

foldr(sum, 1, 2, 3, 4)
--> 10    

You can find other function definitions that leverage this programming style here in Issue #3 through Issue #8.

Lua's sole idiomatic data structure is the table. The table length operator is undefined if there are nils located anywhere in a sequence. Unlike tables, the vararg list respects explicit nils as stated in the basic examples and the remarks section (please read that section if you haven't yet). With little work the vararg list can perform every operation a table can besides mutation. This makes the vararg list a good candidate for implementing immutable tuples.

function tuple(...)
    -- packages a vararg list into an easily passable value
    local co = coroutine.wrap(function(...)
        coroutine.yield()
        while true do
            coroutine.yield(...)
        end
    end)
    co(...)
    return co
end

local t = tuple((function() return 1, 2, nil, 4, 5 end)())

print(t())                 --> 1    2    nil    4    5    | easily unpack for multiple args
local a, b, d = t()        --> a = 1, b = 2, c = nil      | destructure the tuple
print((select(4, t())))    --> 4                          | index the tuple
print(select('#', t()))    --> 5                          | find the tuple arity (nil respecting)

local function change_index(tpl, i, v)
    -- sets a value at an index in a tuple (non-mutating)
    local function helper(n, x, ...)
        if select('#', ...) == 0 then
            if n == i then
                return v
            else
                return x
            end
        else
            if n == i then
                return v, helper(n+1, ...)
            else
                return x, helper(n+1, ...)
            end
        end
    end
    return tuple(helper(1, tpl()))
end

local n = change_index(t, 3, 3)
print(t())                 --> 1    2    nil    4    5
print(n())                 --> 1    2    3    4    5

The main difference between what's above and tables is that tables are mutable and have pointer semantics, where the tuple does not have those properties. Additionally, tuples can hold explicit nils and have a never-undefined length operation.