PrevIndexNext
Syntactic Sugar
Perl offers many ways of making your programs more concise.
Such conciseness may
cause a dramatic increase in the 'cryptic coefficient', however! :)
This is not good.
Once you learn these idioms of the language
they can actually be much clearer than the more long-winded
circuitous manner of writing.
You can write code that is more to the point
and direct. Brevity is the soul of wit.
Michaelangelo is purported to have said:
The essence of Art is the expurgation of the superfluous.
List Literals
Instead of:
my @names = ("one", "two", "three", "four");
use this:
my @names = qw(one two three four);
You save all kinds of pesky punctuation!
Here is another way of writing it which makes it easy
to add or delete elements:
my @names = qw/
one
two
three
four
/;
For the delimiter after the 'qw'
you can use any non alphanumeric character.
(), {}, [], <> can be used in pairs.
my @names = qw{ one two three four };
You can't have elements with embedded spaces.
unless and until
Instead of negating the boolean of an if statement
you can use the keyword 'unless' instead.
These two are equivalent:
if (not ($a < $b)) {
print "hello\n";
}
unless ($a < $b) {
print "hello\n";
}
Similarily there is 'until':
while ($i <= 10) {
...
}
until ($i > 10) {
...
}
Depending on how the boolean is worded these can sometimes be clearer.
'unless' and 'until' are not as commonly used in English
as 'if' or 'while'.
Avoid 'unless' if the boolean is complex or contains negative terms.
Try to not confuse yourself or others!
Statement Modifiers
Remember when we said that even when there is only one
statement in the body of an 'if' statement, still the
curly braces are required? Statement modifiers to the rescue!
Rather than this:
if ($i > 10) {
print "lots!\n";
}
You can say this:
print "lots!\n" if $i > 10;
Using the new syntax saved 8 troublesome keystrokes!
Modifiers can also be used with
qw/while for unless until/.
English has such modifiers so their usage can sound
quite natural:
take_out_garbage if you_love_me;
add_salt until enough;
A common use of such statement modifiers is with 'last' and 'next':
next if substr($line, 0, 1) eq "#";
last if $i > 10;
The totally cryptic $_ - the default variable
This piece of code:
while (my $line = <IN>) {
if (length($line) < 10) {
print $line;
}
}
can be written more concisely:
while (<IN>) {
print if length() < 10;
}
The lines are being read into the variable $_
and it serves as the 'default' variable for
length and print. If you do not provide
a parameter for these two functions (and several others)
they will assume that you are talking about the
variable named $_. It is like a pronoun
such as 'it' or 'he'. It is assumed that
you know who you are talking about based
on context.
The janitor picked up the broom.
He pushed it for the next hour.
This can make for clean and tidy programs
that are concise and clear (sometimes!).
Shift
The 'shift' function that operates on arrays
(by shifting off the first element of the array)
has a default argument.
WITHIN a subroutine
the array defaults to @_ - the array holding
the parameters to the routine.
OUTSIDE of
a subroutine
the array defaults to @ARGV - the array
holding the command line parameters.
die "usage: hello name age\n" unless @ARGV == 2;
my $name = shift;
my $age = shift;
sub ask {
print shift;
my $ans = <STDIN>; chomp $ans;
return $ans eq 'y' || (shift && $ans eq 'Y');
}
print "You are $age years old!\n"
if ask "$name - Are you sure? ", 1;
The code above is rather confusing and could be
termed a 'victim' of overly concise, cryptic,
and obfuscating syntactic sugar. Here is an equivalent
version that is a much better example of good, readable,
modifiable code that is easy to maintain:
#
# the command 'hello' has two command line parameters:
#
# - a name
# - an age
#
# Ask the person if they are sure
# and if they are, print "You are $age years old!".
#
if (@ARGV != 2) {
die "usage: hello name age\n"
}
my ($name, $age) = @ARGV;
#
# Ask a question, get an answer,
# and return true if the answer was affirmative (y or Y)
# otherwise return false.
#
# The subroutine has two parameters:
#
# - a question to ask (preferably without a newline ending)
# - a boolean value to indicate whether
# the answer can be a capital Y
# and not only a lower case y.
#
# Note that an answer of 'yes' will not be accepted as true.
# Also note that in Perl the digit 1 is true and 0 is false.
#
sub ask {
my ($question, $UPPER_Y_is okay) = @_;
print $question;
my $answer = ;
chomp $answer;
if ($answer eq 'y'
||
($UPPER_Y_is_okay && $answer eq 'Y')
) {
return 1;
}
else {
return 0;
}
}
if (ask("$name - Are you sure? ", 1)) {
print "You are $age years old!\n";
}
Note the formatting of an if statement
when the boolean is complex and multi-lined.
For Statement
The for loop over an array can omit the loop variable.
It will default to using $_.
for (@names) {
$tot += length;
}
and since there is only one statement in the body of the loop
the above can be made even more concise:
$tot += length for @names;
Sort Subroutine Sugar
There are many sweeteners for the sort subroutine.
Instead of:
sub numerically {
if ($a < $b) {
return -1;
} elsif ($a == $b) {
return 0;
} else {
return 1;
}
}
@nums = (1, 34, 21, 81, -9);
@snums = sort numerically @nums;
You can do this:
sub numerically {
return $a <=> $b;
}
The "<=>" operator returns -1, 0 or 1 depending
on whether its left operand is numerically less than, equal,
or greater than its right operand. It is affectionately
named the "spaceship" operator
(after some shoot-em-up text-based game). If you want string
comparison use "cmp" instead.
Further, one does not really need to use the 'return' keyword.
The last expression that is evaluated is the value
that is returned. So we have:
sub numerically {
$a <=> $b;
}
For the final brevification one can put the body of
the sort subroutine between the keyword 'sort' and the array.
my @snums = sort { $a <=> $b } @nums;
Clear as mud? Or as clear as an azure sky of deepest summer?
Caution
Be careful with such compactions.
They can quickly become cryptic and unreadable.
There is a fine line between brevity and obfuscation.
For an example of how this kind of 'sport' can
be taken to an absurd extreme, take a look
at Perl Golf!
Exercises
-
Take the following (somewhat nonsensical) program and apply all of the
syntactic sugar that you can. Make it concise,
clean and tidy. Test it at each stage of concisification
to make sure it functions the same way.
my @nums = ("zero", "one", "two", "three", "four");
sub by_length {
if (length $a < length $b) {
return -1;
} elsif (length $a > length $b) {
return 1;
} else {
return 0;
}
}
@nums = sort by_length @nums;
if (not open(IN, "names")) {
print("cannot open names: $!\n");
exit;
}
my $i = 0;
my $total = 0;
while (my $line = <IN>) {
if (not(length($line) <= 4)) {
; # do nothing
} else {
$total = $total + length($line);
print($nums[length($line)], "\n");
++$i;
if ($i % 10 == 0) {
print ".";
}
}
}
close(IN);
print("\n", 'the total is ', $total, "\n");
-
Again, make the following more concise:
sub confirm {
if (@_ != 2) {
print "confirm needs two parameters\n";
exit;
}
my ($prompt, $ok) = @_;
print $prompt;
my $ans = <STDIN>;
chomp $ans;
return $ans eq $ok;
}
my $status = confirm("Are you sure? ", "y");
if (not $status) {
print "That's all folks!\n";
exit;
}
PrevIndexNext