Everyday programming is rarely glamorous -- you have to get your loops straight, your branches right, and your code squeaky clean. Fortunately, Perl makes it easy to write good code with simplified loop constructs, postfix if/unless branching, and a whole lot more. This article takes you through the specific ways in which Perl can help your everyday coding. It is not intended to teach you Perl, but to improve your existing Perl knowledge -- beginner to intermediate.
Perl has the standard procedural looping constructs: the while() loop, the do-until() loop, and the for() loop. These are almost second nature to most programmers. In addition, Perl allows easy iteration over a list of values, or over a range of test conditions.
All of the above loops and the if/unless constructs allow an inverted notation (syntactically, not semantically) in the form EXPRESSION loop or EXPRESSION if/unless as of Perl 5.004. A detailed description of Perl syntax can be found in the perldoc perlsyn page. My recommendation is that you avoid this notation unless you document it well. It can be confusing to some people, especially those with a C/C++/Java background, to see print $_ foreach @array on a line by itself.
The while() loop checks its condition before executing. The following example is a common way to look for input:
Listing 1. The simplest while() loop
while (
)
{
# do something with the current input here
}
|
The code above will execute 0 times if there is no input, or N times for N pieces of input. There is no way to control the number of iterations other than by using next/last statements inside the code.
The do-until() loop is similar to the while() loop, but its contents will get executed at least once. It is more appropriate for conditions that depend on something to have been done, for example:
Listing 2. A simple do-until loop
my $i;
do
{
# put values in @list here...
...
$i = grep(/pattern/, @list);
}
until($i > 0);
|
In the above example, $i did not have a value until the grep was executed, so instead of assigning it a special fake value and then using a while, we can use a do-until loop and follow a more natural flow. Instead of "until," "while" can be used.
Finally, the for() loop takes three arguments as opposed to the one argument of the do-until() and while() loops. This is why the for() loop is the favorite of C programmers everywhere -- it is very easy to write a for() loop that does absolutely everything in one line. There have been instances of programmers writing a for() loop with no body, because all the work is done in the loop arguments themselves. Such condensed code is welcomed at obfuscation contests, but not in production.
The for() has an initialization section (shockingly, usually used for initializing variables), an iteration section (usually a loop counter gets incremented here), and a test section (used to test whether the loop should keep being executed or not). Thus, the equivalent while() and for() loops would be:
Listing 3. Equivalent for() and while() loops
for ($i=0; $i++; $i < 10)
{
# do something 10 times
}
$i=0;
while ($i < 10)
{
# do something 10 times
$i++;
}
|
In the above example, $i got incremented from 0 to 9 (that's 10 values). The for() loop certainly looks cleaner, and it is. Perl, however, is not happy to rest on C's laurels (since the for() loop can be most directly traced to the C language). Perl defines an improved syntax for the for() loop, and aliases it to "foreach." This means that anywhere you can use "for" as a standalone language construct in Perl, you can use "foreach," and vice versa.
For and foreach: how to have fun and get away with it
The improvement mentioned above is that the foreach() loop takes an argument that is a list. That's very useful, because lists are a basic data type in Perl, and many wonderful things can be done with them. For instance, the list of 2 through 40 is written as "2..40" in Perl. For every element of the list, the body of the foreach() loop is executed once. As with the while() and do-until() loops, the "next" and "last" keywords can change the program flow through the loop. "Next" and "last" skip to the next element and exit the loop altogether, respectively. Here are some examples:
Listing 4. The foreach() loop
foreach (1..100)
{
# do something 100 times, $_ will be set to the current number
print "Now on iteration $_\n";
}
foreach (1..20,101..120)
{
# do something 40 times, $_ will be set to the current number
}
foreach my $counter (0..1)
{
# do something twice, $counter will be 0, then 1
}
foreach my $i (0..1000)
{
next unless $i%5; # next if this number is a multiple of 5
print "$i is not a multiple of 5...\n";
next unless $i%7; # next if this number is a multiple of 7
print "$i is not a multiple of 7...\n";
next unless $i%12; # next is this number is a multiple of 12
# $i is now not a multiple of 12, 5, or 7
print "$i is a lucky, lucky number to have met you...\n";
}
# here we use the Perl map operator to create a list of 100 even numbers
# see chapter 5 for details on the map and grep operators
foreach (map { $_ *= 2 } 0..99)
{
print "Even number: $_\n";
|
If, else, elsif, unless, or, how to confuse a cat
Few things are as important to a programmer as understanding and using logical conditions and Boolean algebra in general. It is simply impossible to write a program without logical branching (ask any Computer Science professor if you don't believe me). Perl's logical operators are very similar to C. Consult the perldoc perlop page for specifics on operator syntax.
Several things are different, however. First and foremost for the C/C++/Java refugees, Perl does not normally allow using expressions as the if or else block -- they have to be blocks, not expressions. In other words:
Listing 5. The if or else block
if (something) dothis(); # does NOT work
if (something) # usually works great
{
dothis();
}
dothis() if (something); # my favorite, but see 3.1
# (it should be documented)
|
The unless() statement is immensely useful. Use it. If you find yourself writing the middle of an if(), and the condition has to be inverted before it's checked, you should almost definitely use unless() instead. The common exceptions: you need an elsif() branch, the logical statement controlling the loop is too complex, or you have a problem with inverted Boolean statements that haunts you.
Much like the Monty Python "How to confuse a cat" sketch, Perl's unless() branching logic can be bewildering at first. The concept of unless() didn't originate with Perl, but most Perl novices haven't used a language with it before. Novices can feel intimidated by the power of unless(). It was not, after all, taught in college Computer Science courses like if() -- therefore, sometimes unless() is seen as at best a vagabond, at worst a meddler. Not true. The unless() statement is a different way of thinking. It changes a fundamental control structure from an awkward inversion to a natural form. Compare the following:
Listing 6. If unless, but learn basic Boolean
if (!eof()) ... unless(eof()) ... if (!clear && !ready) ... unless(clear || ready) ... |
The only trick is to invert the logical statement from the inside out -- every individual term is logically negated, and the Boolean operators are inverted too. Basic Boolean algebra is easy to learn (see Resources later in this article), while fixing logic bugs is neither easy nor fun. Invest the time to learn the basics of Boolean algebra, and all your code will benefit.
The road to cleanliness (no shower cap required)
If you miss the lint tool, and the "-Wall -pedantic" compiler switches from your gcc glory days, there is hope.
Use the "-w" flag to tell the Perl interpreter to turn warnings on (similar to the C compiler "-Wall" option, but also applied during runtime, not just during the compilation phase). You can do that by adding the "-w" switch to the interpreter invocation line -- usually the first line of the script in a UNIX environment, and a command-line switch in other environments. Some programmers feel that warnings are only for developers, and that the final product should not have warnings enabled.
First of all, there is no final product in programming. As the famous quote attributed to Jack Cohen goes, "a special word for stable is dead."
Second, the warnings will indicate to a user that something could be wrong, and that pre-emptive action should be taken.
Third, using warnings only during the development cycle is like doing fire drills with real extinguishers, then using paper cups to douse the real fire. It simply doesn't make sense to take away that safety net at an arbitrary point in the product development cycle.
The "use strict" compiler pragma (see the "perldoc strict" and "perldoc perlmodlib" manual pages) is somewhat similar to the C compiler "-pedantic" switch. The "perldoc strict" command will tell you more about that pragma. The most important thing about the "use strict" pragma is that it requires that all variables be defined before they are used. The following example illustrates what "use strict" hopes to avoid:
Listing 7. An error to be avoided by "use strict"
$this_variable_name_is_too_long = 1;
while (
)
{
$this_variabel_name_is_too_long++;
}
|
The error never gets noticed until it is a bug. With "use strict," the above would have never compiled. Instead, the following would have had to be written:
Listing 8. The fixed version of listing 7
my $this_variable_name_is_too_long = 1;
while (
)
{
$this_variable_name_is_too_long++;
}
|
The wisdom of using long variable names at all is... well... let's just say that if you have to type more than 15 characters for a variable name, you are probably doing something wrong.
Do not use functions to define constants. While that was necessary at one point in Perl, it is now done with the "use constant" pragma. In the following example, note the comment style and the arrow alignment as well. Visually pleasing code shows that the author crafted every line of code with care. Some editors (Emacs for instance) can do this sort of alignment automatically.
Listing 9. "use constant" to define constants
use constant CHILDREN => 3; # 3 children per process use constant PI => 3.14; # 2 digits of precision use constant MESSAGE => "Hello"; # a message |
Prototype your functions before they are invoked. It's easy to prototype a function. The "perldoc perlsub" manual page will help you with understanding prototypes better. Prototyping is a good practice for any programmer, because it shows forethought and helps avoid compiler errors in certain cases. It can be tedious to change prototypes as a function changes, but the problem there is that the function was not well-defined from the start. Plan before you code, and think before you plan. Every 10 minutes spent thinking about the path your code will take will save you an hour of coding and debugging.
Perl converts between strings and numbers automatically. There is no way or need to turn this feature off, but novice Perl programmers can create new exciting bugs with it in a matter of hours. Read the Perl FAQ (see Resources), the Programming Perl or Learning Perl books, and of course the "perldoc perldata" manual page ("Scalar values" section).
- Write a loop that counts from 1 to 100, and
- Prints out all even numbers
- Prints out all odd numbers
- Prints out all the numbers that end in 1, 2, or 7
Write a loop that counts down from 100 to 1
Write a program that reads standard input and prints out any lines that begin with "hello" in any case (uppercase, lowercase, or mixed case). Hint: look at the "perldoc perlrun" manual page, and try to use Perl's built-in switches instead of writing your own loops.
Write a logical condition that will check if a scalar is defined, non-zero, a palindrome (the same backwards and forward), or the number 234.98. Test with and without the warnings "-w" flag to the Perl interpreter and the "use strict" compiler pragma. Do you understand the warnings that were generated, if any?
Write the following in normal (non-postfix) notation, and simplify them if you can:
- print if $debug;
- $i++ unless $i > 10;
- unless ($j && !$i) { $j += $i while <> };
- next while <>;
- Read the previous chapters of The road to better programming.
- For related study, take a look at these perldoc pages: perlrun, perlsyn, perlfaq, perlmodlib, strict, and perlop.
- A good tutorial on logic and Boolean algebra is the "Combinational Logic & Systems Tutorial Guide", by David Belton, April 1998.
- For the Perl FAQ, refer to http://www.perl.com/pub/v/faqs or http://perlfaq.cpan.org.
- "Programming Perl Third Edition" by Larry Wall, Tom Christiansen, and Jon Orwant (O'Reilly & Associates, 2000) is the best guide to Perl today, with up-to-date coverage of 5.005 and 5.6.0.
- Read Teodor's other Perl articles in the developerWorks "Cultured Perl" series:
- A programmer's Linux-oriented setup
- Application configuration with Perl
- Automating UNIX system administration with Perl
- Debugging Perl with ease
- The elegance of JAPH
- Genetic algorithms applied with Perl
- One-liners 101
- Parsing with Perl modules
- Perl 5.6 for C and Java programmers
- Reading and writing Excel files with Perl
- Review of Programming Perl, Third Edition
- Small observations about the big picture
- Writing Perl programs that speak English
- Browse more Linux resources on developerWorks.
- Browse more Open source resources on developerWorks.
Teodor Zlatanov graduated with an M.S. in computer engineering from Boston University in 1999. He has worked as a programmer since 1992, using Perl, Java, C, and C++. His interests are in open source work on text parsing, 3-tier client-server database architectures, UNIX system administration, CORBA, and project management.
Comments (Undergoing maintenance)





