Basics of Lua Functions and Recursion

Lua makes use of functions in order to extend the language and to make code a bit cleaner. This section will go over the basics of functions in Lua as well as some basic recursion. We will also look at how to import code into a file in order to make things cleaner.

Introduction to Functions

Basic function definition in Lua is dead simple.

function [name] ([variables])
	[stuff]
end

Functions end up defined in almost the same was as basically any other block in Lua with a control word of function and end to make the logical end of the block. Variables can be added within the parentheses and multiple variables can be added in by adding a comma.

function Do_Stuff (var1, var2, var3)
	[stuff]
end

Functions should always be defined before they are used. Most standards I’ve worked with (granted virtually all of these are adapted from places that started with something like C) dictate that functions are defined after global variables and before the main section of the program. Functions are “first-class values” in Lua, so they can be treated like a standard scalar for all intents and purposes. For instance, you can set a variable to equal a function declaration and use it down the line. This is how table sorting works if you want to use a separate method to sort.

phonebook = { { ["name"] = "John", ["address"] = "1 Fake Street" }, { ["name"] = "James", ["address"] = "1 Real Street" }, { ["name"] = "Kevin", ["address"] = "2 Fake Street" } }
table.sort( phonebook, function ( a, b ) return ( a["name"] < b["name"] ) end )

for i, entry in ipairs( phonebook ) do
	print( entry["name"] .. " = " .. entry[ "address" ] )
end

This will output:

James = 1 Real Street
John = 1 Fake Street
Kevin = 2 Fake Street

We get introduced to a new keyword for functions though, return. Return tells a function to return the value so that you can set a variable to catch the response or use it directly like we do in the function for our sort. This will be expanded on more down the way.

What Is Recursion?

Recursion is basically the use of a function or similar which breaks down the problem and solves it at a small enough level. You can think of it like an onion. As you strip the layers off, you are effectively left with an onion, albeit a smaller one, until you reach the point you split it open and there’s nothing inside. Recursion can make certain solutions to problems easier, but it can be more computationally expensive.

Keep in mind, since functions are “first-class values” in Lua, we can return a function as a result which will call the function then return whatever the function returns. We can then nest these concepts to get tail-recursion. The below is absolutely useless, but illustrates what we can do.

function recurse ( a )
	if a < 100 then return recurse( a + 1 ) end

	return a
end

recurse( 3 ) --returns 100
recurse( 99 ) --returns 100

This should return 100 each and every time (though we don’t do anything with it). We also plant a condition in our code, even though it’s not spelled out. Basically, if a is greater than 100, we return our function recurse( a + 1 ), if we don’t return that, then we will return the value a. Recursive programming requires return conditions otherwise it will run until the program crashes.

An example of where this would be useful is something like finding a factorial. A factorial is defined as n! = n * ( n – 1 ) * ( n – 2 ) … (n – ( n – 1 ) ) where 1! = 1 and 0! = 1. With these definitions, we can either do something like a for loop, or do a recursive method as well.

function factorial ( a )
	if a <= 1 then return a end
	
	return a * factorial( a - 1 )
end

Including Other Code

Lua includes several ways to include other code so that you could put your functions in one file then include it into the main program to make the code cleaner, or include other libraries of functions which may aid with whatever you’re doing. The two main keywords are require and dofile. This link at luafaq.org clears it up nicely. In short, you pass a module to require and a file (or filepath) to dofile.

We’ll get into modules soon enough, but let’s start with dofile. dofile will search in the local path first (i.e. where the program is run from), but can also be used to search and include code from pretty much anywhere. Let’s pretend we have all of our code in the same directory and we have a file like follows:
functions.lua

function do_something ( a )
	return a + 1
end

function do_nothing ( a )
	return
end

function return_cat ( )
	return "cat"
end

And then we have all of our main code in:

main.lua

#!/usr/bin/lua5.1

dofile( 'functions.lua' )

print( do_something( 2 ) )
do_nothing( 1 )
print( return_cat() )

This will run our functions like they are at the top of our code, but then our code becomes much, much more maintainable. This feature to include code and something called metatables are how we handle classes in Lua. If you decide to use something like a library folder or something, you just change the path with dofile.

dofile( 'libraries/functions.lua' )

Please note the direction of the slash in this snippet. Lua files will typically work either way on Windows, but on Linux or MacOS, you want to use forward slashes. I tend to use forward slashes unless I know something will only ever run on Windows. This is a consideration at times, but it’s typically best to write code as portably as possible unless you know for a fact that it will never be used anywhere else.

Conclusion

This basically wraps up the basics of functions in Lua. We have barely touched the surface for a lot of features, but this should be enough to really get going with writing new code and keeping it decently clean. There are different rules of thumb for function length and similar, but that is a whole different can of worms. You typically want to make each function readable and sane to understand what is going on at the very least.

We will go over some exercises next now that we have an easy way to start making our programs more modular and easier to maintain. We will also cover require and using external libraries in order to build much more complicated programs without building everything from scratch. As we get further along in this series, we will be going back and fleshing out some of the earlier features as necessary.