This article is an extension of our previous article on functions. We’re going to learn a few new tricks and see how they can be applied. It also will expand on classes just a little bit.
We’re going to cover passing a variable number of arguments to a function, and how to use functions as variables. Lua has a special construction for passing an arbitrary number of functions without using a table as: an array or a hash table (both are technically tables). We’ll also cover the basics of using a function as a variable and what this can be useful for.
Getting Argumentative
Lua allows a special definition with functions in order to take a variable number of arguments. The construction looks like follows:
function myFunction ([variables,] ... )
--[…]
end
This means that you can pass whatever variables you would normally, but then you can pass whatever you want. The literal symbol for this is three periods (…) and not the single character (…). We saw how to pass our variables, but we need to be able to access them.
Lua makes this trivial with the following construction which can be used in a function:
local argv = {...}
This sets a local variable argv to contain a table of our arguments. Let’s see an example of this all in action with a basic averaging function:
#!/usr/bin/lua5.1
function avg (...)
local argv = {...}
local count = #argv
local result = 0
for i, v in ipairs( argv ) do
result = result + v
end
return result / count
end
print( avg( 1, 1, 1 ) )
print( avg( 1, 2, 3, 4, 5, 6, 7, 8 ) )
We set our local variable argv to contain all of our “unnamed” arguments, which here is every argument. Since it’s a table, we iterate through it with ipairs. We get a count of what’s in our table using the # operator. This gets us:
./functions2.lua
1
4.5
This is all good and well, but what happens when we try to use the function without any arguments?
#!/usr/bin/lua5.1
function avg (...)
local argv = {…}
local count = #argv
local result = 0
for i, v in ipairs( argv ) do
result = result + v
end
return result / count
end
print( avg() )
We get an error:
./functions2.lua
-nan
Argument Mediation
Whenever you use a construction like this, you need to build in error handling. This isn’t a want, but a necessity to make this process scalable and functional. Let’s make our avg function clean:
#!/usr/bin/lua5.3
function avg (...)
local argv = {...}
local count = #argv
local result = 0
if count < 1 then
return nil
end
for i, v in ipairs( argv ) do
result = result + v
end
return result / count
end
print( avg( 1, 1, 1 ) )
print( avg( 1, 2, 3, 4, 5, 6, 7, 8 ) )
local average = avg()
if average ~= nil then
print( average )
else
print( "invalid average" )
end
We’re going to use Lua 5.3 here, but it makes no substantial difference on our execution (all code being used here is going to work between 5.1 and 5.3). We assign the size of our table using the #[table] operator so that we can see if our function has what it should and so that we can get an average. We check to make sure our count is greater than or equal to 1 so that we know our table has at least one element. This prevents a divide by 0 scenario like we got previously.
First-Class Functions
As we learned in our earlier tutorial on functions, functions in Lua are first-class. Functions can be assigned to variables, and functions can be passed around as necessary. We’ve focused primarily on global functions, but Lua also supports local functions Let’s look at a comparison of various function methods.
To start out with, let’s look at the equivalent of a static class in most object-oriented languages:
#!/usr/bin/lua5.3
local static = {}
static.avg = function (...)
local argv = {...}
local count = #argv
local result = 0
if count < 1 then
return nil
end
for i, v in ipairs( argv ) do
result = result + v
end
return result / count
end
print( static.avg( 1, 2, 3 ) )
This gets us:
./staticclass.lua
2.0
As we saw in our code for classes previously, we can have private variables. We can use that to make a private function if we want to. This is similar to what we did with closures in our previous class lesson.
We’re going to do something which isn’t really useful for real life applications to make a point:
#!/usr/bin/lua5.3
local square = {}
square.__index = square --this makes our class work
function square.new(side)
local self = setmetatable({}, square)
-- internal processing for our class instantiation
local setside = function ( self, n ) self.side = n end
setside( self, side )
-- end of internal processing
return self
end
function square:getside()
return self.side
end
function square:getarea()
return self.side * self.side
end
local mySquare = square.new(5)
print( mySquare:getside() )
print( mySquare:getarea() )
This gets us:
./class-extended.lua
5
25
We can use some syntactic sugar to clean this up. Lua allows the construction:
local [variable name] = function ([variables]) […] end
To become:
local function [name] ([variables]) […] end
Let’s use this in our class:
#!/usr/bin/lua5.3
local square = {}
square.__index = square --this makes our class work
function square.new(side)
local self = setmetatable({}, square)
-- internal processing for our class instantiation
local function setside( self, n ) self.side = n end
setside( self, side )
-- end of internal processing
return self
end
function square:getside()
return self.side
end
function square:getarea()
return self.side * self.side
end
local mySquare = square.new(5)
print( mySquare:getside() )
print( mySquare:getarea() )
That’s interesting, but what does it get us? We have basically stumbled back onto the closure type method of classes used previously. Remember how we used:
#!/usr/bin/lua5.1
-- 'square' definition
local square = {}
square.new = function(myside)
local self = {}
-- internal processing for our class instantiation
side = myside
self.getside = function() return side end
self.getarea = function() return side * side end
-- end of internal processing
return self
end
local mySquare = square.new(5)
mySquare.side = 100 -- this doesn't do anything!
print( "square side: " .. mySquare:getside() )
print( "square area: " .. mySquare:getarea() )
Now we can see why this really works. We’re basically using local functions, but using a different approach. We’re not revolutionizing our coding, but we’re gaining a new understanding.
Functions In Action
We saw how useful local functions can be, but let’s see where they really shine. This is another trivial example, but we’ll conceptually spice it up soon enough:
#!/usr/bin/lua5.3
function isBigger( a, b, func )
return func( a, b )
end
local function iComp( a, b ) return a > b end
local function sComp( a, b ) return a:len() > b:len() end
print( isBigger( 5, 1, iComp ) )
print( isBigger( 1, 5, iComp ) )
print( isBigger( 2, 2, iComp ) )
print( isBigger( "ant", "a", sComp ) )
print( isBigger( "z", "cat", sComp ) )
This gets us:
./localfun.lua
true
false
false
true
false
We can pass a function just like anything else to another function. Let’s take a look at an implementation of quicksort. The quicksort algorithm is one of the most efficient sorts across the board. It has very few use cases where it is not efficient (an example being mostly sorted data). Let’s look at a quick implementation of the quicksort in Lua:
#!/usr/bin/lua5.3
function quicksort( array, l, r, comp )
l = l or 1
r = r or #array
if l < r then
local p = partition( array, l, r, comp )
quicksort( array, l, p - 1, comp )
quicksort( array, p + 1, r, comp )
end
end
function partition( array, l, r, comp )
local q = array[ r ]
local i = l - 1
for j = l, r - 1 do
if comp( q, array[ j ] ) then
i = i + 1
array[ i ], array[ j ] = array[ j ], array[ i ]
end
end
array[ i + 1 ], array[ r ] = array[ r ], array[ i + 1 ]
return i + 1
end
local mess = { 1, 2, 6, 5, 4, 10, 42, 1, 2, 1 }
print( "original:" )
for i, v in pairs( mess ) do
io.write( v .. ", " )
end
local clean = mess -- we do this just to make it a little more readable below
quicksort( clean, 1, #clean, function ( a, b ) return a >= b end )
print( "\ncleaned:" )
for i, v in pairs( clean ) do
io.write( v .. ", " )
end
print()
We could format this better, but we get the following on run:
./qsort.lua
original:
1, 2, 6, 5, 4, 10, 42, 1, 2, 1,
cleaned:
1, 1, 1, 2, 2, 4, 5, 6, 10, 42,
Notice what we do with the comp variable we pass. We just pass an anonymous function and use it to sort with. We could use this same algorithm to sort strings as long as the function we pass can compare a and b and define which is “larger”. We won’t go over the quicksort in too much detail here, because it’s explained in depth here.
Conclusion
A lot of these features, aside from the extra parameters, aren’t really all that useful on their own, but they enable a deeper understanding of what functions mean in Lua and what they’re useful for. They enable some of the more complicated features which we’ve touched on briefly but never got too deep into. We reinvented the wheel to see what exactly all went into it with some of the class function options. We also saw how to use anonymous functions to pass to functions and perform other useful tasks with. You can use a dispatch table or similar for functions to have handling for different types and different data based on what it’s for this way.
Exercises
Combine tables and functions to create a dispatch table for a function to format different information. Make it able to take a type of plain, numeric, currency. Here’s a quick example to build off of:
#!/usr/bin/lua5.3
function pickdispatch( value, _format )
dispatch = {}
function dispatch.formatstring( string )
return string.format("formatstring: %s", string )
end
function dispatch.formatnumber( number )
return string.format( "formatnumber: %d", number )
end
if _format == nil then
_format = type( value )
end
if _format == "number" then
return dispatch.formatnumber( value )
else
return dispatch.formatstring( value )
end
end
local string = "here's my string"
local number = 12345
print( pickdispatch( string ) )
print( pickdispatch( number ) )
This is really ugly. 2. Make a version where you can pass a dispatch table to the function. 3. Next, make a version in a class where you don’t need a dispatch table passed to it.