Lists and Arguments in Perl

In this conclusion to a six-part article series on subroutines and functions in Perl, you’ll learn more about lists and arrays, and take a look at default argument values. This article was excerpted from chapter six of the book Beginning Perl, Second Edition, written by James Lee (Apress; ISBN: 159059391X).

Lists Are One Dimensional

Recall that all lists and all arrays are one dimensional. If we have this list:

(@a, @b)

it becomes a one-dimensional list containing the contents of @a followed by the contents of @b . This is an important rule when it comes to passing arrays into functions, since they will be passed in as a one-dimensional list. This is illustrated in the following example:

#!/usr/bin/perl -w
# passarrays.pl

use strict;

my(@nums1, @nums2);
@nums1 = (2, 4, 6);
@nums2 = (8, 10, 12);

process_arrays(@nums1, @nums2);

sub process_arrays {
my(@a, @b) = @_;

print "contents of @an";
print "[$_] " foreach @a;
print "nn";

print "contents of @bn";
print "[$_] " foreach @b;
print "n";
}

This program creates two 3-element arrays, @nums1 and @nums2 . These arrays are then passed into process_arrays() and are immediately copied into two arrays, @a and @b . We might think that @a receives the contents of @nums1 and @b receives the contents of @nums2 , but that is not what happens. Since the arguments are passed in as

process_arrays(@nums1, @nums2);

the elements are flattened into this one-dimensional list:

(2, 4, 6, 8, 10, 12)

and this list is passed in and assigned to the assignable list:

my(@a, @b) = @_;

Since this assignable list contains an array, @a , it will consume all the elements that are assigned to it. Therefore, @b will be empty because there are no elements remaining to assign to it. So, when we execute this program, we will see that @a contains all the elements passed in and @b contains no elements:

$ perl passarrays.pl
contents of @a
[2] [4] [6] [8] [10] [12]

contents of @b

$

Later, when we discuss references in Chapter 11, we will see how to pass two arrays (or hashes) into a function and treat them as two separate variables.

{mospagebreak title=Default Argument Values}

One thing that’s occasionally useful is the ability to give the arguments for your subroutine a default value. That is, give the argument a value to use in the subroutine if one is not specified when the subroutine is called. This is very easily done with the || operator (the logical or operator).

|| has a very special feature: it returns the last thing it evaluates. So, for instance, if we say $a = 3 || 5 , then $a will be set to 3. Because 3 is a true value, the or operator has no need to examine anything else, and so 3 is the last thing the || evaluates. If, however, we say $a = 0 || 5 , then $a will be set to 5; 0 is not a true value, so the operator looks at the next operand, 5, which is the last thing it evaluates. This behavior is called short circuiting.

Hence, anything we get from @_ that doesn’t have a true value can be given a default with the || operator. We can create subroutines with a flexible number of parameters and have Perl fill in the blanks for us:

#!/usr/bin/perl -w
# defaults.pl

use strict;

log_warning("Klingons on the starboard bow", "Stardate 60030.2");
log_warning("/earth is 99% full, please delete more people");
log_warning();

sub log_warning {
my $message = shift || "Something’s wrong";
my $time = shift || localtime; # Default to now.
print "[$time] $messagen";
}

$ perl defaults.pl
[Stardate 60030.2] Klingons on the starboard bow
[Wed May 5 04:07:50 2004] /earth is 99% full, please delete more people
[Wed May 5 04:07:51 2004] Something’s wrong
$

One by-product of specifying defaults for parameters is the opportunity to use those parameters as flags. Your subroutine can then alter its functionality based on the number of arguments passed to it.

{mospagebreak title=Named Parameters}

One of the more irritating things about calling subroutines is that you have to remember the order of the parameters. Was it username first and then password, or host first and then user-name, or . . . ?

Named parameters are a neat way of solving this. What we’d rather say is something like this:

logon( username => $name, password => $pass, host => $hostname);

and then give the parameters in any order. Now, Perl makes this really, really easy because that set of parameters can be thought of as a hash:

sub logon {
die "Parameters to logon should be even" if @_ % 2;
my %args = @_;
print "Logging on to host $args{hostname}n";

}

Whether and how often you use named parameters is a matter of style; for subroutines that take lots of parameters, some of which may be optional, it’s an excellent idea. For those that take two or three parameters, it’s probably not worth the hassle.

Named parameters also help when we want to provide default values to our arguments. For instance, let’s say we write a function named college_degree() and it expects three arguments: university , degree , year . We could call the function with all three arguments:

college_degree(
university => ‘Illinois’,
degree => ‘MSEE’,
year => 2000
);

Since we are using named parameters, the order of those three argument pairs is not important—they could be in any order we want. We could also call the function with only two pairs as in

college_degree (
degree => ‘MSEE’,
year => 2000
);

provided our function defaults the arguments. This implementation of college_degree() ensures that the three arguments have default values:

sub college_degree {
my %args = @_;

$args{university} = ‘Northwestern’ unless exists $args{university};
$args{degree} = ‘BSCS’
unless exists $args{degree};
$args{year} = 2004
unless exists $args{year};


}

Summary

Subroutines are a bit of code with a name, and they allow us to do two things: chunk our program into organizational units, and perform calculations and operations on pieces of data, possibly returning some more data. The basic format of a subroutine definition is

sub name BLOCK

We can call a subroutine by just saying name if we’ve had the definition beforehand. If the definition’s lower down in the program, we can say name() , and you may see &name used in older programs. Otherwise, we can use a forward definition to tell Perl that name should be interpreted as the name of a subroutine. The conventional notation is name() .

When we pass arguments into a subroutine, they end up in the special array @_ —this contains aliases of the data that was passed so data is passed in by reference. We discussed ways of passing variables in by value (copying the arguments into my() variables) and also how to implement default argument values with named parameters.

Exercises

  1. Write a program that computes the factorial of a number. Just to remind you, the factorial of a number is that number times that number minus 1 and so on, stopping at 1. For instance, the factorial of 5 is

    5! = 5 * 4 * 3 * 2 * 1

    The factorial of 0 is 1.
  2. Modify the seconds.pl program seen earlier in the chapter so that it contains a second subroutine that asks the user for a number, puts it into a global variable, and converts that into hours, minutes, and seconds.

 

[gp-comments width="770" linklove="off" ]
antalya escort bayan antalya escort bayan