Wednesday, December 31, 2008

Building custom subroutines in Perl

Every programming language comes with its own built-in functions or subroutines—every time you print() or join() something in Perl, you're actually using a built-in subroutine. But Perl also allows you to define your own custom subroutines, so that you can save yourself some time and effort when performing common tasks. I'll show you why subroutines are useful, and how to create your own.

Advantages of subroutines

Subroutines are convenient for three reasons:

  1. They let developers break up long procedural scripts into smaller, easier to understand fragments. This makes code easier to debug, and simplifies locating the source of errors.
  2. By identifying commonly-used tasks and then encapsulating those tasks into independent packages, subroutines make code reuse a reality. It's not uncommon for many Web developers to maintain their own library of commonly-used subroutines and import them whenever needed to quickly accomplish common tasks.
  3. A subroutine is created once but invoked many times. So if a code update is needed in the future, the changes can be done in one spot (the subroutine definition) while the subroutine invocations remain untouched.

Defining a subroutine

Here's a simple example of a subroutine:

# define subroutine
sub makeJuice
{
print "Making lemon juice...";
}

Every subroutine follows a few basic rules:

  1. The subroutine definition begins with the "sub" keyword, followed by the name of the subroutine. This name is what you use to invoke the subroutine in your scripts. The name may optionally be followed by parentheses.
  2. The code that makes up the subroutine is enclosed within curly braces. This code is regular Perl code—you can use variables, loops, conditionals and all the usual Perl constructs inside it.
  3. Subroutines may appear anywhere in a Perl script, or may even be imported from external files.

To call a subroutine, invoke it by preceding its name with an ampersand (&):

# call subroutine
&makeJuice();

When the Perl interpreter sees this call, it looks for the subroutine named makeJuice() and executes it. You can invoke the same subroutine as many times as you like. In this particular example, we call the makeJuice() subroutine four times:

#!/usr/bin/perl

# define subroutine
sub makeJuice
{
print "Making lemon juice...\n";
}

# call subroutine
&makeJuice();
&makeJuice();
&makeJuice();
&makeJuice();

The above script produces the following output:

Making lemon juice...
Making lemon juice...
Making lemon juice...
Making lemon juice...

Of course, there will come a time when you're fed up with all that lemon juice and would prefer something different. That's why subroutines can accept arguments, user-defined values passed to the subroutine when it is called and then processed by the code inside that subroutine. Let's see how to pass arguments to a subroutine.

Passing arguments to a Perl subroutine


Suppose I would like to give the makeJuice() subroutine more intelligence by telling it which flavor of juice to make:

&makeJuice("strawberry");

Invoking a subroutine with an argument is the easy part—I still need to write the code that accepts the argument and does something with it:

sub makeJuice
{
# retrieve the argument
my ($flavor) = shift (@_);

# and use it
print "Making $flavor juice...\n";
}

Perl has a somewhat unique way of handling subroutine arguments. All arguments passed to a subroutine are stored in a special @_ array. To retrieve the arguments, you have to look inside the array and extract them.

In the revised subroutine definition above, we use the shift() function to extract the first element of the array—the flavor—and assign it to a variable. This variable is then used in the call to print().

If you don't like the shift() syntax, you can also use "regular" array notation (indexing):

sub makeJuice
{
# retrieve the argument
my ($flavor) = $_[0];

# and use it
print "Making $flavor juice...\n";
}

This next listing is another, slightly more useful example:

#!/bin/perl
# define subroutine to convert between dollars and euros sub convertCurrency {
# get amount in $
my ($usd) = shift (@_);

# specify conversion rate
my $convRate = 0.82;

# print amount in euro
print "USD $usd = ", sprintf("%0.2f", $usd * $convRate), " EUR"; }

# invoke function with custom $ amount
&convertCurrency(100);

Here, the convertCurrency() subroutine performs the conversion between dollars and euros. The amount of USD to be converted is passed to the subroutine as an argument, and the output contains the corresponding amount in euros. Adding arguments to a subroutine thus immediately makes the subroutine more flexible and useful.

Sending back return values from a subroutine

Now, consider this variant on the convertCurrency subroutine from the previous example:

#!/usr/bin/perl

# define subroutine
sub convertCurrency
{
# get amount in $
my ($usd) = shift (@_);

# specify conversion rate
my $convRate = 0.82;

# convert value
$euro = $usd * $convRate;
}

# invoke function with custom $ amount
print &convertCurrency(100);

Even though the subroutine does not print any output, it does return a value, which can be caught and used by the main script. By default, this value is the last expression evaluated by the subroutine.

If you like, you can override this return value by specifying your own with a "return" statement:

#!/usr/bin/perl

# define subroutine to check file status
sub checkFileStatus {
# get file path
my ($file) = $_[0];

# test file status
# return 1 or 0
(-r $file) ? return 1 : return 0;
}

# invoke function with filename
# check return value and print appropriate message
$status = &checkFileStatus('/usr/local/mail.cf');

if ($status == 1)
{
print "File is readable\n";
} else {
print "File is not readable\n";
}

The "-r" test checks if the file is readable, and the subroutine sends back true or false to the caller depending on what it finds. The main script then checks this return value and prints an appropriate message.

You can also write a subroutine that returns an array instead of a scalar value:

#!/usr/bin/perl

# define subroutine to
# split an email address into
# user and domain
sub breakEmailAddress
{
# get address
my ($address) = shift(@_);

# split address on the @ symbol into an array
@components = split('@', $address);

# return array
return @components;

}

# split email address
# print the components
@output = &breakEmailAddress('john@some.domain.com');
print "Username is ", $output[0], "\nDomain is ", $output[1]; [/code]

Here is the output:

Username is john
Domain is some.domain.com

0 comments:

Post a Comment