You are not logged in.

Applications: [GameMaster: OPEN] | [Volunteer Testers: OPEN]


This forum will be permanently shut down on Friday 13.07.2018
Please copy or save all important information from old forum before they will be deactivated
We have moved to new board. https://forum.runesofmagic.gameforge.com/Come join us.

Peryl

Intermediate

  • "Peryl" started this thread

Posts: 313

Location: Elsewhere

  • Send private message

1

Thursday, February 2nd 2012, 12:30am

[repost] Using Lua Logic Operators

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.
2013... The year from hell....