Wednesday, August 6, 2008

Comparisons

An iffy start
An if statement is simple. if the day is Sunday, then lie in bed. A simple test, with two outcomes. Perl conversion (don't run this):
if ($day eq "sunday") {
&lie_in_bed;
}

You already know that &lie_in_bed is a call to a subroutine. We assume $day is set earlier in the program. If $day is not equal to 'Sunday' &lie_in_bed is not executed (pity). You don't need to say anything else. Try this:
$day="sunday";

if ($day eq "sunday") {
print "Zzzzz....\n";
}

Note the syntax. The if statement requires something to test for Truth. This expression must be in (parens), then you have the braces to form a block.

The Truth According to Perl
There are many Perl functions which test for Truth. Some are if, while, unless . So it is important you know what truth is, as defined by Perl, not your tax forms. There are three main rules:

Any string is true except for "" and "0".
Any number is true except for 0. This includes negative numbers.
Any undefined variable is false. A undefined variable is one which doesn't have a value, ie has not been assigned to.
Some example code to illustrate the point:
&isit; # $test1 is at this moment undefined

$test1="hello"; # a string, not equal to "" or "0"
&isit;

$test1=0.0; # $test1 is now a number, effectively 0
&isit;

$test1="0.0"; # $test1 is a string, but NOT effectively 0 !
&isit;

sub isit {
if ($test1) { # tests $test1 for truth or not
print "$test1 is true\n";
} else { # else statement if it is not true
print "$test1 is false\n";
}
}
The first test fails because $test1 is undefined. This means it has not been created by assigning a value to it. So according to Rule 3 it is false. The last two tests are interesting. Of course, 0.0 is the same as 0 in a numeric context. But it is not the same as 0 in a string context, so in that case it is true.

So here we are testing single variables. What's more useful is testing the result of an expression. For example, this is an expression; $x * 2 and so is this; $var1 + $var2 . It is the end result of these expressions that is evaluated for truth.

An example demonstrates the point:

$x=5;
$y=5;

if ($x - $y) {
print '$x - $y is ',$x-$y," which is true\n";
} else {
print '$x - $y is ',$x-$y," which is false\n";
}

The test fails because 5-5 of course is 0, which is false. The print statement might look a little strange. Remember that print is a list operator? So we hand it a list. First item, a single-quoted string. It is single quoted because it we do not want to perform variable interpolation on it. Next item is an expression which is evaluated, and the result printed. Finally, a double-quoted string is used because we want to print a newline, and without the doublequotes the \n won't be interpolated.

What is probably more useful than testing a specific variable for truth is equality testing. For example, has your lucky number been drawn?

$lucky=15;
$drawnum=15;

if ($lucky == $drawnum) {
print "Congratulations!\n";
} else {
print "Guess who hasn't won!\n";
}

The important point about the above code is the equality operator, == .




Equality and Perl
Now pay close attention, otherwise you'll end up posting an annoying question somewhere. This is a FAQ, as in a Frequently Asked Question.

The symbol = is an assignment operator, not a comparison operator. Therefore:

if ($x = 10) is always true, because $x has been assigned the value 10 successfully.
if ($x == 10) compares the two values, which might not be equal.
So far we have been testing numbers, but there is more to life than numbers. There are strings too, and these need testing too.


$name = 'Mark';

$goodguy = 'Tony';

if ($name == $goodguy) {
print "Hello, Sir.\n";
} else {
print "Begone, evil peon!\n";
}


Something seems to have gone wrong here. Obviously Mark is different to Tony, so why does perl consider them equal?

Mark and Tony are equal -- numerically. We should be testing them as strings, not as numbers. To do this, simply substitute == for eq and everything will work as expected.




All Equality is Not Equal: Numeric versus String
There are two types of comparison operator; numeric and string. You've already seen two, == and eq. Run this:
$foo=291;
$bar=30;

if ($foo < $bar) {
print "$foo is less than $bar (numeric)\n";
}

if ($foo lt $bar) {
print "$foo is less than $bar (string)\n";
}


The lt operator compares in a string context, and of course < compares in a numeric context.

Alphabetically, that is in a string context, 291 comes before 30. It is actually decided by the ASCII value, but alphabetically is close enough. Change the numbers around a little. Notice how Perl doesn't care whether it uses a string comparison operator on a numeric value, or vice versa. This is typical of Perl's flexibility.

Bondage and discipline are pretty much alien concepts to Perl (and the author). This flexibility does have a drawback. If you're on a programming precipice, threatening suicide by jumping off, Perl won't talk you out of your decision but will provide several ways of jumping, stepping or falling to your doom while silently watching your early conclusion. So be careful.




An interlude -- The Perl Motto
The Perl Motto is; "There is More Than One Way to Do It" or TIMTOWTDI. Pronounced 'Tim-Toady'. This tutorial doesn't try and mention all possible ways of doing everything, mainly because the author is far too lazy. Write your Perl programs the way you want to.




The Comparison Operators Listed
The rest of the operators are: Comparison Numeric String
Equal == eq
Not equal != ne
Greater than > gt
Less than < lt
Greater than or equal to >= ge
Less than or equal to <= le




The Golden Rule of Comparisons
They may be odious, but remember the following:
if you are testing a value as a string there should be only letters in your comparison operator.
if you are testing a value as a number there should only be non-alpha characters in your comparison operator
note 'as a' above. You can test numbers as string and vice versa. Perl never complains.


More About If: Multiples
More about if statements. Run this:

$age=25;
$max=30;

if ($age > $max) {
print "Too old !\n";
} else {
print "Young person !\n";
}

It is easy to see what else does. If the expression is false then whatever is in the else block is evaluated (or carried out, executed, whatever term you choose to use). Simple. But what if you want another test ? Perl can do that too.

elsif

$age=25;
$max=30;
$min=18;

if ($age > $max) {
print "Too old !\n";
} elsif ($age < $min) {
print "Too young !\n";
} else {
print "Just right !\n";
}

If the first test fails, the second is evaluated. This carries on until there are no more elsif statements, or an else statement is reached. An else statement is optional, and no elsif statements should come after it. Logical, really.

There is a big difference between the above example the one below:


if ($age > $max) {
print "Too old !\n";
}

if ($age < $min) {
print "Too young !\n";
}


If you run it, it will return the same result - in this case. However, it is Bad Programming Practice. In this case we are testing a number, but suppose we were testing a string to see if it contained R or S. It is possible that a string could contain both R and S. So it would pass both 'if' tests. Using an elsif avoids this. As soon as the first statement is true, no more elsif statements (and no else statement) are executed.

You don't need to take up a whole three lines:


print "Too old\n" if $age > $max;
print "Too old\n" unless $age < $max;


I added some whitespace there for aesthetic beauty. There are other operators that you can use instead of if and unless , but that's for later on.

Incidentally, the two lines of code above do not do exactly the same thing. Consider a maximum age of 50 and input age of 50. Therefore, you should be very careful about your logic when writing code (nice obvious statement there).

For those that were wondering, Perl has no case statement. This is all explained in the FAQ, which is located at http://www.perl.com/.

No comments: