Lua doesn’t have true ternary operator but there is a way to mimic the behavior. This is one of the biggest issues people who aren’t used to Lua have when reading Lua code. For the most part Lua is simple and straight forward to understand. Assuming a blocky and easy to understand style is used. However, the pseudo ternary is often used and trips up people. So let’s look at it in some detail.
The core of a ternary is an if ... else
statement. In Lua, we can mimic this
using and ... or
with the evaluated statement. What we end of is if
true
then use the code path after and
or if
false use the code path after the
or
. Let’s look at a simple example.
if a == nil then
b
else
c
end
This is the equivalent to:
a == nil and b or c
Let’s look at more complex logic.
if a ~= nil and (1 < 5) then
b
else
c
end
We can do the same thing with the and ... or
syntax. We just need to use
parenthesis like we would with an if
statement to separate what applies to
which part of the statement
(a ~= nil and (1 < 2 or 4-2 > 5)) and b or c
We do need to be very careful with the separation. For this to work it always
has to be (statement) and (statement) or (statement)
. Notice the and
and
or
cannot be within parentheses. Here is a bad example that doesn’t work like
you’d expect.
a ~= nil and (b or c) .. a
In this situation we’d end up with b
concatenated with a
which is expected.
However, if a == nil
, then we would end up with false
. This statement does
not have a proper or
for the false
code path of the logic. Since there is
no code path Lua returns false
for in this situation. The proper way to
handle sticking a tailing statement to the ternary is to put the entire
operation into parentheses.
(a ~= nil and b or c) .. a
Regardless of which branch is chosen we’ll always get the variable concatenated
with a
.
Now let’s look at complex components components.
a ~= nil and (b .. c .. a) or (a .. c .. b)
a == nil and (b .. c .. a) or (a .. c .. b)
This goes back to proper use of parentheses. We have the decision, the and
and or
outside of any parentheses and the code paths themselves within to
properly contain the logic.
Let’s put together some samples we can run to really demonstrate everything that’s been covered.
local a = "123"
local b = "xyz"
local c = "test"
print("Simple if")
if a == nil then
print("", b)
else
print("", c)
end
print("Equivalent simple ternary")
print("", a == nil and b or c)
print("Complex if")
if a ~= nil and (1 < 2 or 4-2 > 5) then
print("", b)
else
print("", c)
end
print("Equivalent complex ternary")
print("", (a ~= nil and (1 < 2 or 4-2 > 5)) and b or c)
print("Bad ternary")
print("", a ~= nil and (b or c) .. a)
print("", a == nil and (b or c) .. a)
print("Proper ternary")
print("", (a ~= nil and b or c) .. a)
print("", (a == nil and b or c) .. a)
print("Complex ternary")
print("", a ~= nil and (b .. c .. a) or (a .. c .. b))
print("", a == nil and (b .. c .. a) or (a .. c .. b))
Now for the output
Simple if
test
Equivalent simple ternary
test
Complex if
xyz
Equivalent complex ternary
xyz
Bad ternary
xyz123
false
Proper ternary
xyz123
test123
Complex ternary
xyztest123
123testxyz
We should keep looking at this a bit further. In the above examples only variables were used and
they were all run though a print statement. Ternary operations should be small and will typically
only use variables. Such as using it for assignment. However, functions can be run in the ture
and false
branches and care needs to be taken.
Let’s look at an example with two functions.
print("Functions 1")
function b_func()
print("b_func", b)
end
function c_func()
print("c_func", c)
end
print("", true and b_func() or c_func())
You might think only function b_func
would run but that’s not what happens. b_func
has no
explicit return so implicitly it returns nil
, which evolutes to false. true
run the true
branch (b_func
), but since that branch returns nil
/false
it will then continue on and
run the false
branch (c_func
). So we’ll end up with this output:
Functions 1
b_func xyz
c_func test
nil
Both functions ran which is not at all what we’d want to happen. Also notice that since
c_func
also don’t have a return it returned nil
and since it’s inside of a print
statement nil
is printed.
This can cause problems if you’re trying to run a function that either doesn’t have
a return value or if it returns nil
/false
and you don’t care about the return.
The false
branch (c_func
) will only run if the true
branch (b_func
) returns
true
.
This goes back to the fact this is not a true ternary but an evolution!
If we change b_func
to return true like so.
function b_func()
print("b_func", b)
return true
end
Then run it again we’ll get this output.
Functions 1
b_func xyz
true
This is what we want to happen with only b_func
running. We still see true
printed which
is the return of the function because we’re running the return value through print
.
If you’re working with functions you need to be very careful with this pattern otherwise
more than you expect might run. My advise is if you’re calling functions don’t use this
pseudo ternary and instead use a true if
block. Only use this for variable assignment.
However, even with variable assignment you need to be careful with the true
branch.
e = nil
g = true and e or c
What do you think g will be, nil
or test
(c
)?
If you guessed test
(c
) you’re right. Again this
is an evaluation. Our expression is true
, go to the true
(and
) branch. That branch
if false
so evaluate the false
(or
) branch. Since it’s an or where either e
or
c
for the evaluation then c
will run whenever after e
if it is false
.
The next question is how to assign nil
in the true
branch. As far as I’m aware
you can’t. So, use an if
statement if you need nil
assignment. It’s imperative that
you are sure variable used for assignment with this ternary pattern will never be nil
and any functions will either return true
or a value otherwise this won’t work
like you expect.
Hopefully this makes ternaries logic can be used in Lua clear to everyone.