Skip to main content

The road to better programming: Chapter 3

Loops, clean code, and the Perl idioms

Teodor Zlatanov (tzz@iglou.com), Programmer, Gold Software Systems
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.

Summary:  This series of articles on developerWorks comprises a complete guide to better programming in Perl. In this third installment, Teodor gives a quick introduction to the Perl loop syntax, conditional statements, and writing clean code. While not intended to teach Perl from the ground up, this chapter will be useful for the beginner or intermediate Perl programmer interested in learning how to apply Perl better to everyday work.

Date:  01 Dec 2001
Level:  Introductory
Activity:  1322 views

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 loops

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++;
}

For and foreach

Because of the (ahem) rich cultural heritage of the for() loop, I recommend that foreach() be used instead. This is for several reasons:
  • foreach() sounds better in English.
  • foreach() is an explicit break from the C tradition, whereas for() may confuse novice programmers.
  • There's no difference between the two at the interpreter level.

If you feel you must use the for() loop, I suggest that you heavily document its occurrences and your reasoning for its use. While for() has its uses, especially in the three-argument form, it is a tool best avoided by novice and intermediate programmers.

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).


Exercises

  1. 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
  2. Write a loop that counts down from 100 to 1

  3. 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.

  4. 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?

  5. 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 <>;

Resources

About the author

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)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Linux, Open source
ArticleID=11182
ArticleTitle=The road to better programming: Chapter 3
publish-date=12012001
author1-email=tzz@iglou.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers