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

  1. 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
  2. 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