CIT 042 Index > Functions

Functions

To this point, all of our programs have been sufficiently simple that they can be written in one monolithic "block." However, as programs become larger and more complex, you may find some task that needs to be done repetitively, or some subtask that is sufficiently difficult that you'll want to put it in a separate area all by itself.

The official name of such a subtask is called a function. You can think of a function like a recipe. For example, a cookbook may have a recipe for Eggs Benedict, which needs Hollandaise sauce. It might look like this:

  1. Slice an English muffin.
  2. Begin boiling water.
  3. Make Hollandaise sauce as described on page 211.
  4. Poach an egg when water boils.
  5. Put egg on muffin, and slather with Hollandaise sauce.
  6. Eat.

At the end of step 2, you put aside the eggs and muffin, and turn to page 211. After you're done with that recipe, you return to step 4 and continue with the recipe.

A Simple Function

Here's an example of a Perl function, or recipe, that will draw a dividing line. The line numbers are for reference.

1   sub divider
2   {
3       print "-=" x 10, "\n";
4   }
Line 1
A function begins with the word sub followed by the name of the function. Function names follow the same rules as variable names, and are also case sensitive.
Lines 2 through 4
The function's action is enclosed in curly braces.

And here's a program that will use that function several times. The lines in boldface call, or invoke the function. This is done by putting the name of the function, followed by parentheses. The parentheses are required to indicate that this is a function call, and not just some bare word in the middle of nowhere. [We'll see other reasons for the parentheses later.]

print "Squares of numbers:\n"
for ($i=1; $i < 4; $i++)
{
    print($i, " ", $i*$i, "\n");
}
divider();
print "Cubes of numbers:\n";
for ($i=1; $i < 4; $i++)
{
    print($i, " ", $i**3, "\n");
}
divider();
print "Odd numbers:\n";
for ($i=1; $i < 5; $i+=2)
{
    print("$i is odd\n");
}

The output of this program is as follows:

Squares of numbers:
1 1
2 4
3 9
-=-=-=-=-=-=-=-=-=-=
Cubes of numbers:
1 1
2 8
3 27
-=-=-=-=-=-=-=-=-=-=
Odd numbers:
1 is odd
3 is odd

So what have we bought with this? Wouldn't it be much simpler to just put in the print statement instead of creating the function? Well, yes, for this trivial program. The only advantage we've gained is the replacement of an anonymous print statement with a meaningful name. Let's say, though, that you have a larger program that needs thirty dividers, and you copy the print statement to accomplish this. Then you decide that repeated dashes -- look better, and you need fifteen pairs of them, not ten. Because you used a copy-and-pasted print statement, you'll have to find and replace all thirty occurrences. By using the divider() function, however, you just replace the one line in the function body, and presto, all occurrences of the divider line have changed.

Parameters

This function is self-contained, much like the Hollandaise sauce recipe; it doesn't need any extra information from the outside world. Some recipes, however, require extra information:

Pineapple Surprise
Take one slice of pineapple for each serving. Chop well. Mix with two tablespoons of whipped cream per serving. Add one-half teaspoon of hot pepper paste per serving. Top with three broccoli florettes per serving. Surprise! It tastes awful.

You can't make this recipe until you know how many servings are required; someone "outside the recipe" has to provide you with that information.

Consider this task: we want to avoid repetition in doing our debugging. Notice that the two print statements are identical except for the name of the variable and the value that we print.

$x = $y * $z;
print("The value of x is now $x.\n");
$sum = $b + $c;
print("The value of the total is now $sum.\n");

We want a function called debug() that will print out those debugging messages for us. It needs to do something like this, where the italics represent information we need from "outside."

sub debug
{
    print "The value of variableName is value.\n";
}

and our program will "fill in the blanks" by putting the appropriate items in the parentheses of the function call:

$x = $y * $z;
debug("x", $x);
$sum = $b + $c;
debug("the total", $sum);

How, then, does the function "fill in the blanks"? The answer is that the list of arguments that you put in the parentheses is put into a special array called @_, which your function has access to. We will use list assignment to put this list into two variables within the function:

sub debug
{
    ($variableName, $value) = @_;
    print "The value of $variableName is $value.\n";
}

There's only one thing more we need to do. All variable names in Perl are global by default. If our main program were to use the scalar $value for something else, the assignment in debug() would interfere with it. We need some way to say that the variables inside of the debug() function are local. That is, they are for use only by that function, and are to be considered as separate from any other scalar that may happen to have the same name in either our main program or any other function. We do this by preceding the list with the keyword my:

sub debug
{
    my ($variableName, $value) = @_;
    print "The value of $variableName is $value.\n";
}

It is, of course, possible to access the @_ array directly and not assign its elements to the local variables, as in the following example. This is what the book does, but we do not like it, as it is both ugly and difficult to understand. If you have individual parameters wth different meanings, you really want to make local variables. If you are treating the parameters as a true list, which we will see later, then using @_ makes sense.

sub debug
{
    print "The value of $_[0] is $_[1].\n";
}

Returning Values

We've seen lots of built-in functions that return a value. substr takes a scalar variable and some numbers as arguments, and returns the appropriate section of a string. index takes two strings as arguments and returns a number telling where one string was found within the other. Obviously, we'd like our own personal functions to be able to do the same kind of thing. Let's say we'd like a function that figures out the average of three numbers:

$average_dimension = average( $length, $width, $height );

Here's how we do it:

sub average
{
    my ($x, $y, $z) = @_;
    my $result;
    
    $result = ($x + $y + $z) / 3;
    return $result;
}

The second line of the function, my $result makes $result a local variable. The last line, return $result, takes the variable and hands it back to the caller. A return statement causes an immediate exit from the function! You can, for example, have more than one return

sub calc_wages
{
    my ($hours, $rate) = @_;
    my $total_pay;
    
    if ($hours <= 40)
    {
        return ($hours * rate);
    }
    if ($hours <= 60)
    {
        $total_pay = 40 * $rate + ($hours-40) * $rate * 1.5;
        return $total_pay;
    }
    $total_pay = 40 * $rate + 20 * $rate * 1.5 +
        ($hours-60) * 2 * $rate;
    return $total_pay;
}

You will want to use the @_ array directly if you have a variable number of arguments, or if you wish to treat your arguments as a true list. Let's say we would like the average function to be able to work with any number of arguments. We would rewrite it as follows:

sub average
{
    my ($sum, $item, $n);   # local variables
    $sum = 0;
    $n = scalar(@_);
    if ($n == 0)
    {
        return 0;
    }
    foreach $item (@_)
    {
        $sum += $item;
    }
    return $sum / $n;
}

Finally, a function can return a list or a hash as well as a scalar. We can write a stats function that will return the minimum, maximum, and average of a list of numbers, and use it as in this example:

sub stats
{
    my ($sum, $min, $max, $item, $n);
    $n = scalar(@_);
    if ($n == 0)
    {
        return (0,0,0);
    }
    $max = $min = $_[0];
    $sum = 0;
    foreach $item (@_)
    {
        $sum += $item;
        $max = $item if ($item > $max);
        $min = $item if ($item < $min);
    }
    return ($sum/$n, $max, $min);
}
        
$a = 5; $b = 3; $c = 10; $d = 6;
($avg, $biggest, $smallest) = stats( $a, $b, $c, $d );

print("Average is $avg, max is $biggest, min is $smallest\n");