Now let’s look at special SLua ways of calling functions, with optional arguments and variable lists of arguments. In this article, we’ll show how to handle an optional argument.
Suppose we wanted a function to scale numbers by some factor. We might write this test:
assert_equals(scale(2, 5), 10)
assert_equals(scale(2, 7), 14)
And we could code scale like this to pass the test:
function scale(value, factor)
return value * factor
end
And our tests would pass. We are up to 11 now, because I’m still running all the tests from the previous article:
[12:18] JR20250501: Tests: correct 11, wrong 0
But suppose we wanted our scale function to default to a scale of ten. We might write this additional test assertion:
assert_equals(scale(2, 5), 10)
assert_equals(scale(2, 7), 14)
assert_equals(scale(7), 70)
If we run now, we’ll get an error:
JR20250501 [script:testing and stuff] Script run-time error
runtime error
lua_script:33: attempt to perform arithmetic (mul) on number and nil
lua_script:33 function scale
lua_script:94 function tests
lua_script:61 function run_tests
lua_script:3 function state_entry
lua_script:100
That’s because the second argument to scale was not provided and is therefore nil. So we could change our function like this:
function scale(value, factor)
if factor == nil then
factor = 10
end
return value * factor
end
In the code above we just check to see if factor is nil, which it will be if it isn’t provided. And if it is nil, we set it to 10. Now all our assertions pass:
[12:23] JR20250501: Tests: correct 12, wrong 0
It turns out that there is a shorter way of doing that check, using SLua’s logical operator or
, like this:
function scale(value, factor)
factor = factor or 10
return value * factor
end
This is the approved way to handle this situation in Lua and SLua, so we need to understand it. First, we’ll consider a simple logical expression. Suppose we want to do something if age is greater than beauty or weight is less than 100. In LSL we might write:
if ( age > beauty || weight < 100 ) {
do_something();
}
In SLua we write that same thing like this:
if age > beauty or weight < 100 then
do_something()
end
LSL uses ||
to mean “or” and Lua uses or
to mean “or”.
But there is one important difference between the two. In Lua, when you are doing an or
, if the first thing is true, you just use it and don’t evaluate the second part. (And, it turns out, if you are doing an and
and the first bit is false you don’t evaluate the second part.) There’s no point to evaluating the second part because we already know how it has to turn out because true or false
is true
, and false and true
is false
.
And there’s still another Lua oddity that we have to consider. In Lua only nil
and false
are false. Every other value, even zero, evaluates as true.
So when factor is provided, like this:
assert_equals(scale(2, 7), 14)
Inside the function, factor is 7 so this code:
factor = factor or 10
Is the same as
factor = 7 or 19
7 evaluates as true
(we say that 7 is “truthy”) so the second part of the or
does not run and the boolean expression returns the first “true”, namely 7. So factor is 7. But in this call:
assert_equals(scale(7), 70)
The value of factor
is nil, so we evaluate
factor = nil or 10
And since nil
is false
, we evaluate the second part of the or, which is 10, which is truthy, so we return it. And we set factor to 10 as intended.
Now a reasonable person might ask why we even allow things like 10 to be truthy and to be honest, I do not know. But that’s how it works, and since 10 is truthy, it’s a perfectly good result from the boolean, and the shorthand works as we intend.
My advice would be just to learn that notation and go with it, and if you felt like using the longhand version, I wouldn’t be offended. But this short form is kind of the official cool kids way to do it.
I think we’ve said enough on this topic, and it’s enough to take in at one go. I’ll just add that we could check more than one optional argument if we wanted to, but it starts getting pretty strange with more than one optional, at least in my view.
One more thing. If I wanted a function with a lot of optional arguments (and I can’t remember ever wanting that), I would very seriously consider passing in a table of arguments rather than just a straight call. Something ike this:
function long_name(args)
title = args.title or ""
first = args.first or ""
middle = args.middle or ""
second_initial = args_second_middle or ""
last = args.last or ""
-- do whatever we do
end
Then we might call it in these forms:
long_name({first="Janet", last="Rossini"})
long_name({
title="The Honorable",
first-"Jorge",
middle="F",
second_initial="X",
last="McGruder"
})
Again, this is not a thing I recall needing, but if I did, I’d consider something like the above.
Next time, I think we’ll do variable argument lists, called “variadic functions” to confuse us. Safe paths!