Wednesday, August 6, 2008

Logical Operators

Logical operators are such things as OR, NOT, AND. They all evaluate expressions. The expression evaluates to true, or false. Exactly what criteria for evaluation are used depends on the operator.

or
The or operator works as follows:
open STUFF, $stuff or die "Cannot open $stuff for read :$!";

This line means -- if the operation for opening STUFF fails, then do something else. Another example:
$_=shift;

/^R/ or print "Doesn't start with R\n";

If the regular expression is false, then whatever is on the left side of the or is printed. As you know, shift works on @ARGV if no target is given, or @_ inside a subroutine.

Perl has two OR operators. One is the now familiar or and the other is || .




Precedence: What comes First
To understand the difference between the two we need to talk about precedence. Precedence means priority, order, importance. A good example is:
perl -e"print 2+8

which we know equals 10. But if we add:
perl -e"print 2+8/2

Now, will this be 2+8 == 10, divided by 2 == 5? Or maybe 8/2 == 4, plus 2 == 6?

Precedence is about what is done first. In the example above, you can see that the division is done first, then the addition. Therefore, division has a higher precedence that addition.

You can force the issue with parens:

perl -e"print ((2+8)/2)

which forces Perl, kicking and screaming, to evaluate 2+8 then divide the result by 2.

So what has this to do with logical operators? Well, the main difference between or and || is precedence.

In the example below, we attempt to assign two variables to non-existent elements of an array. This will fail:

@list=qw(a b c);

$name1 = $list[4] or "1-Unknown";

$name2 = $list[4] || "2-Unknown";

print "Name1 is $name1, Name2 is $name2\n";

print "Name1 exists\n" if defined $name1;
print "Name2 exists\n" if defined $name2;


The output is interesting. The variable $name2 has been created, albeit with a false value. However, $name1 does not exist. The reason is all about precedence. The or operator has a lower precedence than || .

This means or looks at the entire expression on its left hand side. In this case, that is $name1 = $list[4] . If it is true, it gets done. If it is false, it is not and the right hand side is evaluated, and the left hand side is ignored as if it never existed. In the example above, once the left side is found to be false, then all the right side evaluates is "1-Unknown" which may be true but doesn't produce any output.

In the case of || , which has a higher precedence, the code immediately on the left of the operator is evaluated. In this case, that is $list[4]. This is false, so the code immediately to the right is evaluated. But, the original code on the left which was not evaluated, $name2 = is not forgotten. Therefore, the expression evaluated to $name2 = "2-Unknown".

The example below should help clarify things:

@list=qw(a b c);

$ele1 = $list[4] or print "1 Failed\n";
$ele2 = $list[4] || print "2 Failed\n";

print <ele1 :$ele1:

ele2 :$ele2:

PRT

The two failure codes are both printed, but for different reasons. The first is printed because we are assigning $ele1 a false value, so the result of the operation is false. Therefore, the right hand side is evaluated.

The second is printed because $list[4] itself false. Yet, as you can see, $ele2 exists. Any idea why?

The reason is that the result of print "2-Failed\n" has been assigned to $ele2. This is successful, and therefore returns 1.

Another example:

$file='not-there.txt';

open FILE, $file || print "1: Can't open file:$!\n";

open FILE, $file or print "2: Can't open file:$!\n";


In the first example, the error message is not printed. This is because $file is evaluating to true. However, in the second example, or looks at the entire expression, not just what is immediately to the left and takes action on the result of evaluating the entire left hand side, not just the expression immediately to its left.

You can fix things with parens:

$file='not-there.txt';

open FILE, $file || print "1: Can't open file:$!\n";

open FILE, $file or print "2: Can't open file:$!\n";

open (FILE, $file) || print "3: Can't open file:$!\n";


like so, but why bother when you have a perfectly good operator in or ? You could apply parens elsewhere:
@list=qw(a b c);

$name1 = $list[4] or "1-Unknown";

($name2 = $list[4]) || "2-Unknown";

print "Name1 is $name1, Name2 is $name2\n";

print "Name1 exists\n" if defined $name1;
print "Name2 exists\n" if defined $name2;

Now, ($name2 = $list[4]) is evaluated as a complete expression, not just as $list[4] is evaluated as a complete expression, not just as $list[4], so we get exactly the same result as if we used or .

And
now for something similar. And. Logical AND operators evaluate two expressions, and return true only if both are true. Contrast this with OR, which returns true only of one or more of the two expressions are true. Perl has a few AND operators.

The first type of AND we will look at is && :

@list=qw(a b c);

print "List is:@list\n";

if ($list[0] eq 'x' && $list[2]++ eq 'd') {
print "True\n";
} else {
print "False\n";
}

print "List is:@list\n";

The output here is false. It is clear that $list[0] does not equal x . As AND statements can only return true if both expressions being evaluated are true, then as the first statement is false this is an obvious non-starter and perl decides it need not continue to the second statement. Entirely sensible.

The second type of AND statement is & . This is similar to && . See if you can work out what the difference is using this example:

@list=qw(a b c);

print "List is:@list\n";

if ($list[0] eq 'x' & $list[2]++ eq 'd') {
print "True\n";
} else {
print "False\n";
}

print "List is:@list\n";

The difference is that the second part of the expression is evaluated no matter what the result of the first part is. Despite the fact that the AND statement cannot possibly return true, perl goes ahead and evaluates the second part of the statement anyway, hence $list[2] ends up as d .

The third AND which we will look at is and . This behaves in the same way as && but is lower precedence. Therefore, all the guidelines about || and or apply.


Other Logical Operators
Perl has not , which works like ! except for low precedence. If you are wondering where you have seen ! before, what about:
$x !~/match/;

if ($t != 5) {

as two examples. There is also Exclusive OR, or XOR. This means:
If one expression is true, XOR returns true
If both expressions are false, XOR returns false
If both expressions are true, XOR returns false (the crucial difference from OR)
This needs an example. Jane and Sonia are two known troublemakers, with a reputation for throwing good beer around, going topless at inappropriate moments and singing out of tune to the karaoke machine. You only want to let one of them into your party, and instead of a big muscle-bound bouncer you have this perl script on the door:
($name1,$name2)=@ARGV;

if ($name1 eq 'Jane' xor $name2 eq 'Sonia') {
print "OK, allowed\n";
} else {
print "Sorry, not allowed\n";
}

I would suggest running it thus:
perl script.pl Jane Karen
(one true, one false)

perl script.pl Jim Sonia
(one true, one false)

perl script.pl Jane Sonia
(both true)

perl script.pl Jim Sam
(both false)

Well, the script is not perfect as a doorman, as all Jane and Sonia have to do is type their names in lowercase, but hopefully it demonstrated xor .

One thing to beware of is:

$_=shift;

print "OK\n" unless not(!/r/i || /o/i & /p/ or /q/);

over-complication, and believe me the above is not as complicated as it could be. Take the time to understand what you want to do. Perl provides a plethora of logical operands so you really don't have any excuse for not writing legible code. The above can be written a lot more concisely and clearly. As well as a lot more obscurely :-)

@ARGV