PrevIndexNext
Subroutines and Sorting
Subroutines (a.k.a. procedures or functions or methods)
are (as you might expect by now) rather
simple and straightforward in Perl.
Declaring and Calling a new subroutine
sub hello {
print "hello how are you?\n";
}
The name can be any valid Perl identifier.
Note that the indentation style is similar
to that of the 'if' or 'while'.
hello; # prints "hello how are you?"
Subroutines are very useful for breaking a long
program up into manageable pieces. Giving a
sequence of lines a meaningful name will also
add to the clarity of your program.
init;
process;
show_results;
clean_up;
If you declare the subroutine BELOW
the place where you call it you need
to either precede the name with an ampersand '&'
or use parentheses:
&bye; # or bye()
sub bye {
print "adios!\n";
}
Input parameters
You can pass input parameters to the subroutine in the
same way that you pass parameters to the Perl built-in
functions like 'substr' or 'index'.
The parameters get magically assigned to a special array very cryptically
named @_. Bizzare, huh? The first element of the array
@_ is referenced (as usual) with $_[0]. Looks odd, huh?
sub hello {
print "Well done $_[0]! You did it $_[1] times!\n";
}
hello("Joe", 3); # Well done Joe! You did it 3 times!
Alternatively:
sub hello {
($name, $n) = @_;
print "Well done $name! You did it $n times!\n";
}
The above is the normal and recommended way to handle parameters.
It gets copies of the parameters and names them.
Local variables
An even better way to handle input parameters is to copy
them to variables that are entirely private to the subroutine:
sub hello {
my ($name, $n) = @_;
...
}
With the 'my' keyword the variables $name and $n can be seen ONLY
within the scope of the subroutine. This
prevents the subroutine from clobbering any
similarily named variables outside of itself.
This can easily happen - especially when a
program grows in size and you tend to use
the same variable name a lot - such as $i.
NOTE - be sure to put the 'my' variables
within parentheses. This makes the assignment happen in list (or array)
context.
You can often (but not always) omit the parentheses on a
subroutine call because the semi-colon ';'
will tell Perl where the parameters end.
Strict
The above use of 'my' within a subroutine is such a good idea
that it is recommended everywhere. You can be alerted
to places where you did not use it. At the top of every script
it is a best practice to always put these two lines:
use strict;
use warnings;
With these you must use 'my' when first using scalars and arrays.
If you don't, the program will fail to compile. This is a good thing.
my ($i, $n, $x);
my @nums = (1, 2, 4, 8);
my @people = get_people();
for my $p (@people) {
...
}
The 'use warnings' will also give you hints about several other things that
might need adjustment.
How many parameters?
What if you are passing a variable number of parameters?
How do you know how many you are receiving?
Well, they are put in the array @_. What is the size of that array?
As usual you can get the size of an array by assigning @_
to a scalar or by putting it in scalar context.
sub show {
my $n = @_;
if ($n < 2) {
die "need two or more parameters for 'show'\n";
}
my ($name, $age) = @_;
...
}
More cryptically you could have done this:
@_ >= 2 or die "need two or more parameters for 'show'!\n";
Ponder on that one! Sometimes what at first appears
overly concise and cryptic can eventually become very clear
and clean.
Returning a Value
Subroutines often want to return a value to the caller
in the same way that the 'index' built-in function does.
It makes for a clean and clear syntax. Here are some
fairly complex examples. Study them carefully.
sub discrim {
my ($a, $b, $c) = @_;
return $b**2 - 4*$a*$c;
}
sub confirm {
my ($question) = @_;
print "$question ";
my $ans = <STDIN>;
chomp $ans;
my ($first) = substr($ans, 0, 1);
if ($first eq "y" or $first eq "Y") {
return 1; # true
}
else {
return 0; # false
}
}
print discrim(1, 3, 4), "\n";
if (confirm("Are you sure?")) {
print "Okay, I'll do it!\n";
}
Subroutines in Perl can actually return multiple values.
Sorting Arrays
Sorting arrays is done with the keyword 'sort'.
@names = ("Joe", "Paul", "gretchen", "Alley", "Bunny");
@snames = sort @names;
print "@snames\n"; # Alley Bunny Joe Paul gretchen
This sorts the array alphabetically (or ASCIIbetically).
Capital letters come before lower case. This may not
always be what you want. For example it will not work
for sorting a series of numbers.
To sort in a different order you must supply the sort function
with a different comparison routine.
@nums = (9, 3, 4, 10, 1);
sub numerically { # whatever name you wish
if ($a < $b) {
return -1;
}
elsif ($a == $b) {
return 0;
}
else {
return 1;
}
}
@snums = sort numerically @nums;
print "@snums\n"; # 1 3 4 9 10
Whenever 'sort' needs to compare two elements
of the array it will call the subroutine 'numerically'.
The elements are 'magically' passed by way of
the names $a and $b. It is the job of the sort subroutine
to look carefully at $a and $b and return -1, 0 or 1.
It will:
return-1 if $a is less than $b
return 0 if $a is equal to $b
return 1 if $a is greater than $b
You can see the process of sorting in action by inserting
a print in the sort subroutine:
sub numerically {
print "We are comparing $a and $b\n";
...
}
'numerically' will be called many many times. This is a different
way of using a subroutine. You do not call it yourself.
It is called for you by the sort function whenever it needs
to compare two numbers.
Exercises
-
Make a subroutine to return the
maximum value of its two parameters.
print max(4, 7); # 7
print max(4, 1); # 4
Extend your subroutine to allow any number of values as input.
print max(1, 3, 2, 61, 2); # 61
-
Read a series of words from a file
and print them sorted by the length of the
word. This input:
Harry
Penelope
Joe
Paul
results in this output:
Joe
Paul
Harry
Penelope
PrevIndexNext