This is a repost as the original went the way of the dodo. I took the opportunity to do a small addition/update to the de Morgan's law section that I should have done a while ago
[SIZE=+2]Introduction[/SIZE]
With the introduction of DIYCE 2.0, some people have been attempting to convert and update their older DIYCE code to the new version with varying success. One problem that has cropped up a few times already is in the use of Lua's logic operators when writing up the
use entry for DIYCE's skill list.
Of course, the information here can be applied to more than just DIYCE, and therefore covers more than just DIYCE usage. This guide is my attempt to explain the ins and outs of Lua's logic operators and some of the rather unique uses they can be put to.
[SIZE=+2]Boolean Logic[/SIZE]
Boolean logic (a part of Boolean algebra) is the algebra of two values or the logical calculus of truth tables. Sounds scary but the basics are actually pretty easy to grasp.
The operators in boolean logic are conjunction (and), disjunction (or), and negation (not). Since Boolean logic uses only two values, we normally use values of true and false (or just T and F), though you will also often find literature using 1 and 0 instead of T and F respectively (this occurs more often when dealing with the algebra aspects and also in bitwise operations on computers).
To understand how these operators work, we use something known as truth tables. These are tables showing all the possible inputs and outputs of a given operator. So without further ado, here are the tables:
|
Source code
|
1
2
3
4
5
6
7
|
and or not
A B | Result A B | Result A | Result
------------- ------------- -----------
F F | F F F | F F | T
F T | F F T | T T | F
T F | F T F | T
T T | T T T | T
|
What these tables show are the possible inputs to the operator (the A and B columns) and the result produced when we apply the operator on these inputs (the Result column).
What you need to remember about these tables is that the
and and
or operators have two inputs and the
not operator only has one. Also, note how the
and table produces a result of true
if and only if both inputs are true, while the
or operator will have a result of false
if and only if both inputs are false. As for the
not operator, it merely flips the value of the input.
Well that pretty much covers most of what you need to know about Boolean logic (there's a little something called de Morgan's laws, but we'll cover that at the end, and it is mostly optional).
There is of course much more to the field of Boolean logic than shown here, but you don't need to know it for using these operators in Lua. Though if you are interested in learning more, here's a link to the
Wiki page about Boolean Logic.
[SIZE=+2]Lua's Use of Boolean Logic[/SIZE]
Lua uses Boolean logic pretty much as you'd expect given the above truth tables, but has a few
gotchas that you need to be aware of.
Lua uses the terms
and,
or, and
not as its logic operators. Not exactly rocket science which operators they refer to now is it? Further, the values Lua uses are... wait for it...
true and
false (yeah, I thought you'd be flabbergasted).
Here we come to our first
gotcha because the above isn't the whole picture. Ask yourself: What happens when a value that isn't
true or
false is used with one of Lua's logic operators?
Lua treats all values that aren't
true or
false (for example a number or a string) as being
true except for the value
nil, which is considered to be
false, when used with a logic operator (this is a little different from many programming languages out there where a number value of 0 is also considered false, but in Lua, 0 is considered true). Any variable that hasn't actually been defined is treated as having a value of
nil.
There is also a second complication involving the resulting value, but we'll get to that in a bit.
Given the above and our trusty truth tables, we can determine the results produced by the following lines of Lua code (read each line seperately):
|
Source code
|
1
2
3
4
5
6
7
|
result = true and true -- result will be true (see truth table)
result = false and true -- result will be false
result = not true -- result will be false
result = false or true -- result will be true
result = false or false -- result will be false
result = not nil -- result will be true (remember, nil is considered false here)
result = 0.1 and "yes" -- result will be true (well sort of, but we'll get to that)
|
Of course, we would normally use variables instead of direct values as shown here, but this is easier to see.
[SIZE=+2]Short-Circuited Logic and Lua[/SIZE]
Lua uses something called short-circuited logic (sometimes called short-cut evaluation) when dealing with its logic operators. This is done mainly for execution speed reasons, but it also has some side-effects that can come in handy but must still be kept in mind or things may not act as you would expect.
All short-circuited logic boils down to is that if a result of a logic operator can be determined before the entire thing is evaluated, then just return the known result without bothering to do the rest of the processing.
This is possible because of two factors. First is the order Lua evaluates logic operators. Lua does its evaluation left to right. That is, when evaluating the line
result = false and true, Lua will determine the value on the left of the
and operator (the
false part in this case), and then if needed, will evaluate the right side (the
true part). The second factor comes from the truth tables themselves. Note how the truth table for
and will give a result of true only if both inputs are true, while the truth table for
or will give a result of false only if both inputs are false.
So if the left side of an
and operator is
false, the result
must be
false regardless of what the right side evaluates to (for the
or operator, if the left side is
true then the result will also be
true regardless of the value on the right side). This fact is taken advantage of by Lua and it will skip the evaluation of the right side of the operator.
As mentioned, Lua evaluates non true or false values as
true except
nil which evaluates to false. However, it doesn't convert these values to a true or false value when used with
and or
or. What this all means is that because of this short-cut evaluation, Lua's
and operator will return its first argument (the value on the left) if it evaluates to false, while for Lua's
or operator, it will return its first argument if it evaluates to true.
Knowing all this allows us to use the logic operators in new and interesting ways. For example, lets say you have written a function that takes the name of a item to use. What would happen if the name of the item is never specified when calling the function? Lua will treat this as
nil and so it may cause problems in the code. We could of course do an if statement to catch this condition, but we can also use the logic operators like this:
|
Source code
|
1
2
3
4
5
6
|
function SomeFunction(itemName)
itemName = itemName or ""
-- do some interesting things here
end
|
If no parameter was given when this function is called, then when the first line of this function is executed the left side of the
or operator will be
nil and therefore false. Since the left side of the
or statement is false, Lua will return the empty string ("") as the result (this is also correct from the logic standpoint, since the empty string is neither the value
nil nor
false and so is considered true). The assignment therefore becomes
itemName = "".
However, if a name is given as a parameter, then the left side is evaluated as the string value passed in (and hence
true) so Lua will have no need to evaluate the right side of the
or operator and this makes the assignment become
itemName = itemName.
[SIZE=+2]Chaining Logic Operators[/SIZE]
Often we want to use more than one logic operator at a time but as we'll see, we must keep in mind a few more rules.
First, lets look at what happens when we use more than one
and statement like this:
|
Source code
|
1
|
result = A and B and C
|
Since Lua evaluates its logic operators left to right, it will perform
A and B to get a value, then use that value with the
and C part. In other words,
A and B and C is equivalent to
(A and B) and C.
Nothing really unexpected so far, but what happens when the logic operators used are not all the same? For example, the following does not evaluate as you would expect:
|
Source code
|
1
|
result = A or B and C
|
Here Lua will evaluate the
B and C first, then do the
A or part with the intermediate value. Put another way, the above is actually equivalent to
A or (B and C). This happens because of something called operator precedence.
Operator precendence is the order of importance that is associated with each operator that Lua uses. Operators with higher precendence will get evaluated first. Here's the table of operator precedence in Lua. Operators at the top of the list are evaluated before those further down, operators on the same row are evaluated as they are encountered.
|
Source code
|
1
2
3
4
5
6
7
8
|
^
not - [I](unary minus)[/I]
* /
+ - [I](subtraction)[/I]
..
< > <= >= ~= ==
and
or
|
All binary operators (that is, the operators that take two parameters) are left associative (that is the left side is evaluated first) except for exponentiation (^) and concatention (..) which are right associative (the right side is evaluated first). See
Programming in Lua, Section 3.5 - Precedence for more details.
From this table, note how the
not operator is quite high on the list, and
or is at the very bottom with
and just above it. Because of this order
and has higher precedence than
or while
not has a higher precedence than either of the other two.
Keeping this table in mind, we can now see why mixing the
or and
and statements previously gave the order it did. If in doubt however, use parentheses around the operators you want to make sure are done first.
[SIZE=+2]Simulating the Conditional Operator[/SIZE]
Many computer languages have a special operator known as the conditional operator (sometimes called the ternary operator). In C and C++, this is the
?: operator and it pretty much functions like an if-else statement.
Though Lua does not have such an operator, everything we've learned so far about Lua's logic operators allows us to simulate the conditional operator.
As a simple example, lets say you wanted to find the maximum value of the two number variables A and B (yes, Lua already has a function to do this, we're only using this as an example). We could of course use an if-else statement, but we can do this with Lua's logic operators as well. Consider the following:
|
Source code
|
1
|
result = (A > B) and A or B
|
Since
and is evaluated before
or, we must consider that part first, then worry about the
or statement.
If A is greater than B then
(A > B) is true and therefore
(A > B) and A will give the value A (the right side of the
and). Now when Lua evaluates the
or, it has a value of A on the left of the
or, which it considers true, and so it doesn't bother evaluating the B, giving an equivalent assignment of
result = A, which is what we want in this case.
If A happens to be less than or equal to B, then
(A > B) is false so
(A > B) and A will also be false since Lua won't even bother evaluating the right side of the
and. This value of false is then used as the left side of the
or which makes Lua evaluate the right side of the
or and as that is a number value, it is considered true and B will be returned as the value giving us an equivalent assignment of
result = B. Again, this is what we wanted in this case.
To get a better idea of what is going on, lets re-check this by creating a truth table of all the parts as Lua interprets them.
|
Source code
|
1
2
3
4
|
(A > B) and A or B | result
------- ----- ---- | ------
true A A | A
false false B | B
|
In the table, I show the values Lua will interpret instead of just true or false since it is relevant. Just remember how Lua interprets these values and how it does short-circuited logic.
[SIZE=+2]de Morgan's Laws[/SIZE]
As promised, we'll touch on the topic of de Morgan's laws. Don't worry, it isn't all that complicated really, and you don't need to memorize much. In fact, this is more of an optional section really.
de Morgan's laws are rules relating the
and and
or operators in terms of the other operator and the
not operator.
So what is this good for anyway? Knowing how the
and and
or operators relate to each other can help simplify your logic when you have a complex chain. Also, given how Lua uses short-circuited logic, it can be beneficial to re-state an
or operator in terms of
and and vice-versa.
In English, de Morgan's laws are:
- The negation of a conjunction is the disjunction of the negations.
- The negation of a disjunction is the conjunction of the negations.
Still sounds a little scary but when seen as logic operators we get:
- not (A and B) = (not A) or (not B)
- not (A or B) = (not A) and (not B)
Doesn't look as bad now does it? We can do better still though.
Another way of looking at these laws is to view them as follows (this really shows the relationship between the
and and
or operators):
- A and B = not ( (not A) or (not B) )
- A or B = not ( (not A) and (not B) )
Starting to look pretty straight forward now. Notice how both are in effect doing the exact same thing. In short, to change the logic operator from
and to
or or vice-versa, you invert (not) both inputs (the left and right sides), change the operator, and finally invert (not) the entire thing. That's all you really need to remember about de Morgan's laws (or law really).
We can prove de Morgan's laws by using a truth table. Here I'll use
! to indicate
not or the column headings will get pretty big (
! is C/C++'s not operator).
|
Source code
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
A B A and B !A !B !A or !B !(!A or !B)
- - ------- -- -- -------- -----------
F F F T T T F
F T F T F T F
T F F F T T F
T T T F F F T
A B A or B !A !B !A and !B !(!A and !B)
- - ------ -- -- --------- ------------
F F F T T T F
F T T T F F T
T F T F T F T
T T T F F F T
|
We can clearly see that the third column in these tables are equivalent to the last column, proving de Morgan's laws.
To learn more about de Morgan's laws, see
this Wiki page.