Today a intern at Kidoteca, asked me a interesting question while learning Lua:
“Why print(4 and 5) prints the number 5?”
This took me a little by surprise, although I use some Lua logical operators hackery, I did not really remember how they worked… So I went to read a bit the specifications, and found the answer to be interesting enough to warrant this article.
Lua has three logical operators, they are and , or and not .
Conversion to boolean
They follow the same rules as the control structures to consider what is true , and what is false . Lua considers everything true , unless it is the boolean value false , or nil .
1 2 3 4 5 6 7 8 9 10 11 |
local function printBoolean(argument) if argument then print("true") else print("false") end end printBoolean("false"); --string, true printBoolean(5); --number, true printBoolean(false); --boolean false, false printBoolean(nil); --nil, false printBoolean(true); --boolean true, true printBoolean("string"); --string, true printBoolean({}); --empty table, true printBoolean({a=5}); --not empty table, true printBoolean(function() end); --empty function, true printBoolean(print); --not empty function, true |
Logical operators behaviour
not is simple enough, it converts whatever is to the right of it to a boolean value and negate it.
1 2 3 4 5 6 7 8 |
--the print boolean is not needed anymore, because we want to print the output of "not" directly print(not "false") --false print(not not "false") --true print(not false) --true print(not true) --false print(not 5) --false print(not {}) --false print(not nil) --true |
and returns the first argument if it is false, otherwise the second. We will explore the reason for that, and how it can be exploited later.
1 2 3 4 5 |
print(1 and 5) --1 is true, thus prints second argument: 5 print(false and 5) --false is false, thus prints first argument: false print(nil and true) --nil is false, thus prints first argument: nil print(true and false) --true is true, thus prints second argument: false print(true and 42) --true is true, thus prints second argument: 42 |
or do the exact opposite of and , it returns the second argument if the first one is false.
1 2 3 4 5 |
print(1 or 5) --1 is true, thus prints first argument: 1 print(false or 5) --false is false, thus prints second argument: 5 print(nil or true) --nil is false, thus prints second argument: true print(true or false) --true is true, thus prints first argument: true print(true or 42) --true is true, thus prints first argument: true |
Short-circuit evaluation
Also, both or and and stop evaluations as soon they can, meaning that in case of an and , it does not evaluate the second argument if the first is false, and or does not evaluate the second if the first is true.
1 2 3 4 5 6 7 8 9 |
local function firstTrue() print("first operand"); return true end local function secondTrue() print("second operand"); return true end local function firstFalse() print("first operand"); return false end local function secondFalse() print("second operand"); return false end local a = firstTrue() and secondTrue() --prints first operand, second operand local a = firstFalse() and secondTrue() --prints first operand local a = firstTrue() or secondTrue() --prints first operand local a = firstFalse() or secondTrue() --prints first operand, second operand |
The last particular property is useful for many things, in my opinion the two most important are performance savings (ie: stop checking stuff if you already know the result) and checking something about a data inside a table.
For example, if you want to check if myTable.myData is loaded, you can just check if myData exists, but what if myTable does not exist? You get a nasty runtime error! The solution is use the and operator and also check for the existence of myTable .
1 2 3 4 5 6 7 8 9 10 11 12 |
local myTable = {}; if myTable.myData then print("myData exists") else print("myData does not exists") end --prints myData does not exists myTable.myData = 1; if myTable.myData then print("myData exists") else print("myData does not exists") end --prints myData exists myTable = nil; if myTable.myData then print("myData exists") else print("myData does not exists") end --Runtime Error because we tried to get the element of something nil if myTable and myTable.myData then print("myData exists") else print("myData does not exists") end --myTable is nil, thus and stops evaluating --no attempt is made to read myData from myTable |
OR and AND (lack of) type conversion
People coming from other languages, like C for example, might be confused: “Why Lua logical operators return an operand, instead of returning true or false ?”. This is because there is no need to do otherwise. Lets evaluate ourselves the following example.
1 2 3 4 5 |
if 5 and false or "string" and not print then print("true"); else print("false"); end |
The first part is decide evaluation ordering, in Lua the operator or has the lowest precedence of all, being the last thing evaluated, and is just a bit higher, and not is one of the highest, thus not is evaluated first, if we edit our code and put the result in place it becomes:
1 2 3 4 5 |
if 5 and false or "string" and false then print("true"); else print("false"); end |
not print became false , because print was a function, and it became true , and the not negated it, becoming false . Now the left-most and operator is evaluated.
1 2 3 4 5 |
if false or "string" and false then print("true"); else print("false"); end |
The operands were 5 and false, since 5 is true (because everything that is not false , or nil , is true), and the and operator in that case returns the second argument, it returned false . Now we will evaluate the second and .
1 2 3 4 5 |
if false or false then print("true"); else print("false"); end |
This time we had as operators "string" and false , since any string is true, and returned false (that was created by the not print ). Now we evaluate the or .
1 2 3 4 5 |
if false then print("true"); else print("false"); end |
The or returns the second argument when the first argument is false, since the second argument was false too, the result is false, so the program now will print "false" .
And in case of true? For example 1 and 5 . In this case, and will return 5 to the if (because 1 is true), and when the if clause evaluates the 5, it will convert it to true . Note this is two evaluation steps, different from how C++ works for example, where the and operator will check if both sides are true and return true or false .
Logical operator hacks
Logical operators because the way they work in Lua allow some interesting constructions, the most popular one is to have default values.
1 2 3 4 |
local function doSomething(someInput) local someVar = someInput or "this is a default value!!" return someVar .. " something!" end |
Like we said before, or returns the second argument if the first is false or nil , this mean that you cannot use this constructions if you want to keep false (or nil ) as a valid value.
The other use is imitate C ternary (?:) operator, x = a ? b : c; results into b getting assigned into x if a is true, and c getting assigned into x if a is false.
1 2 3 |
local courtSide = (ball < centerLine) and "left side" or "right side" --if ball is < centerLine, courtSide will be "left side" --otherwise it will be "right side" |
It works by first evaluating the and , if the first operand is false, and will return a false, that in turn will force the or to return the second operand. If the first operand is true, and will return the second operand, if the returned operand is not false, or will return it, this also means that the second operand must not be false or nil , otherwise this construction will fail.