Expanding Lua Functions

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.