HOME  |    TRAINING  |   FREE TUTORIALS   |   JOBS
Find out more about our new RSS feed.
FREE Tutorial
PROFESSIONAL PERL PART 3 - PROTOTYPES

CATEGORY
SEARCH OUR OTHER TUTORIALS

DESCRIPTION

The subroutines we have considered so far exert no control over what arguments are passed to them; they simply try to make sense of what is passed inside the subroutine. For many subroutines this is fine, and in some cases allows us to create subroutines that can be called in a variety of different ways.
Click here to be kept informed of our new Tutorials.


This free tutorial is a sample from the book Professional Perl Programming.


For example, we can test the first argument to see if it is a reference or not, and alter our behavior accordingly. However, we are not enforcing a calling convention, so we will only discover our subroutines are being called incorrectly when we actually execute the call, and then only if we have written the subroutine to check its arguments thoroughly. Since some subroutine calls may not occur except under very specific circumstances, this makes testing and eliminating bugs very difficult.

Fortunately there is a way to define compile-time restrictions on the use of subroutines through the use of prototype definitions. Although entirely optional, by specifying the types of the expected parameters, prototypes can eliminate a lot of the problems involved in ensuring that subroutines are called correctly. This allows us to specify what parameters a subroutine takes (scalars, lists/hashes or code references), and whether a parameter can be either a simple literal value, or whether it must be an actual variable. Good use of prototypes early in the development process can be invaluable.

A prototype definition is a parenthesized list of characters mirroring the Perl variable type syntax (that is, $, @, %, and so on). It is placed after the sub keyword and subroutine name but before anything else, be it a subroutine definition, declaration, or anonymous subroutine:

sub mysub (PROTOTYPE);  # subroutine declaration
sub mysub (PROTOTYPE) {...}  # subroutine definition
$subref = sub (PROTOTYPE) {...}  # anonymous subroutine

Defining the Number of Parameters and their Scope

Prototypes allow us to explicitly define how many arguments a subroutine expects to receive. This is something that for efficiency reasons we would clearly prefer to check at compile-time. We do not have to wait until the subroutine call is used to find out that it is faulty, and passing the wrong number of parameters is an obvious candidate for a bug.

To illustrate, consider the volume subroutine that we defined in various different forms earlier. With the exception of the named argument example, the subroutine expects three scalar parameters. Using prototypes we can enforce this by adding ($$$), meaning three mandatory scalar arguments, to the subroutine definition:

sub volume ($$$) {
# ... as before ...
}

With this prototype in place, volume can only be called with three scalar arguments. They can be literals or variables, but there must be three of them, and they must be scalar. Hence, this is legal:

print volume(1, 4, 9), "\n";  # displays 1 * 4 * 9 == 36

This, however, is not. Even though it provides the right number of values, it doesn't supply them in a way that fits the prototype:

@size = (1, 4, 9);
print volume(@size), "\n";

Instead, we get the error:

Not enough arguments for main::volume at ... near @size

So far, so good. However, due to Perl's concept of context, prototypes do not enforce things quite as strictly as this might imply. The prototype does not actually enforce a data type - it attempts to force it. What the first $ in the prototype actually does is force @size to be interpreted in scalar context and not as a list, in other words, it is exactly as if we had written:

print volume(scalar @size), "\n";

Having turned the three element array into a scalar '3', the prototype goes on to interpret the second argument as a scalar also. It then finds there isn't one, and produces an error. The fact that we passed an array is not relevant, since an array can be converted to a scalar. However, by passing just one array, we omitted two mandatory arguments, which is important. To illustrate this, the following actually works just fine, the array not withstanding:

print volume(@size, 4, 9);  # displays 3 * 4 * 9 == 108

We have not supplied three scalars, but we have supplied three values which can be interpreted as scalars, and that's what counts to Perl.

We can also use @ and % in prototype definitions, and it is sometimes helpful to consider subroutines without prototypes as having a default prototype of (@); that is:

sub mysubroutine (@) {...}

Just like unprototyped subroutines, the single @ prototype will absorb all values, flattening any lists or hashes it finds. It follows from this that a prototype of (@,@) is just as invalid as it was before. However, if we want to enforce an array variable, as opposed to a mere list, that's a different story, as we will see shortly.

A @ or % prototype matches all parameters in the argument list from the point it is defined to the end of the list. Indeed, % and @ are actually identical in meaning to Perl, since passing a hash turns it into a list. Recall that there is no such thing as 'hash context'. It cannot check that passed parameters came from a hash due to flattening, nor that the remaining parameters divide evenly into pairs because that is a run-time issue. However, this does not mean they are of no use. It means that the only useful place for either prototype character is at the end of the prototype. As an example, here is a subroutine, which joins array elements incorporating a prefix and suffix. It takes a minimum of three parameters, but has no maximum because of the @ prototype:

#!/usr/bin/perl
# join.pl 
use warnings;

sub wrapjoin ($$$@) {
my ($join, $left, $right, @strings) = @_;
foreach (@strings) {
  $_ = $left. $_. $right;
  }
return join $join, @strings;
}

print wrapjoin("\n", "[","]", "One", "Two", "Three");

Without the @ we could only pass three arguments. If we added more $ characters we could allow more, but then we would be forced to supply that many arguments. The @ allows an arbitrary number, so long as we also supply three scalars to satisfy the initial $$$.

Lists can validly be empty, so the prototype does not ensure that we actually get passed something to join. We could attempt to fix that by requiring a fourth scalar, like this:

sub wrapjoin ($$$$@) {
($join, $left, $right, @strings) = @_;
}

However, a little thought reveals a flaw in this design. A literal list of strings works fine, but if the caller supplies an actual array variable for the fourth argument it gets converted to a scalar. In effect, we have introduced a new bug by adding the prototype.

The moral here is that prototypes can be tricky and can even introduce bugs. They are not a universal band-aid for fixing subroutine calling problems. If we want to detect and flag an error for an empty list, prototypes cannot help us - we will have to write the subroutine to handle it explicitly at run-time.

Prototyping Code References

Other than $, @ (and the synonymous %), we can supply one other basic prototype character: &. This tells Perl that the parameter to be supplied is a code reference to an anonymous subroutine. This is not as far-fetched as it might seem; the sort function accepts such an argument, for example.

Here is how we could prototype the do_list subroutine we introduced when we covered anonymous subroutines earlier:

sub do_list (&@) {
my ($subref, @in) = @_;
my @out;
foreach (@in) {
  push @out, &$subref ($_);
}
return @out;
}

The prototype requires that the first argument be a code reference, since the subroutine cannot perform any useful function on its own. Either a subroutine reference or an explicit block will satisfy the prototype; for example:

@words = ("ehT", "terceS", "egasseM");
do_list {print reverse($_[0] =~/./g), "\n"} @words;

Note how this syntax is similar to the syntax of Perl's built-in sort, map and grep functions.

Subroutines as Scalar Operators

We mentioned previously that subroutines can be thought of as user-defined list operators, and used much in the same way as built-in functions (that also work as list operators) like print, chomp, and so on. However, not all of Perl's functions are list operators. Some, such as abs, only work on scalars, and interpret their argument in a scalar context (or simply refuse to execute) if we try to supply a list.

Defining subroutines with a prototype of ($) effectively converts them from being list operators to scalar operators. Returning to our capitalize example, if we decided that, instead of allowing it to work on lists, we want to force it to only work on scalars, we would write it like this:

sub capitalize ($) {
$_[0] = ucfirst (lc $_[0]);
}

However, there is a sting in the tail. Before the prototype was added this subroutine would accept a list and capitalize the string in the first element, coincidentally returning it at the same time. Another programmer might be using it in the following way, without our knowledge:

capitalize (@list);

While adding the prototype prevents multiple strings being passed in a list, an array variable still fits the prototype, as we saw earlier. Suddenly, the previously functional capitalize turns the passed array into a scalar number:

@countries = ("england", "scotland", "wales");
capitalize (@countries);

The result of this is that the number '3' is passed into capitalize. Since this is not a variable, it causes a syntax error when we try to assign to $_[0]. If we chose to return a result rather than modifying the passed argument, then the code would all be perfectly valid, but badly bugged. However, a program that is used to print 'England' might start printing '3' instead. This is more than a little confusing, and not intuitively easy to track down.

The key problem here is not that we are passing an array instead of a scalar, but that we are checking for a scalar value rather than a scalar variable, which is what we actually require. In the next section we will see how to do that.

Continued...


NEXT PAGE



5 RELATED COURSES AVAILABLE
MICROSOFT VISUAL BASIC V6 INTRODUCTION
To go from the fundamentals of Visual Basic programming to the threshold of Advanced level. Gaining in depth prog....
MICROSOFT VISUAL BASIC 5.0 PROFESSIONAL INTRODUCTION
To provide readers with a solid foundation upon which to build Windows applications using Visual Basic 5. Readers....
MICROSOFT VISUAL BASIC 5.0 CLIENT SERVER DEVELOPMENT
This course teaches the skills required to develop client server applications using MS Visual Basic 5.0 Enterpris....
C++ PROGRAMMING
Object oriented programming is fast becoming the leading software design methodology, with C++ becoming ever more....
C PROGRAMMING
This course is design to provide non-C programmers with the essential skills and knowledge necessary to allow the....
 
0 RELATED JOBS AVAILABLE
CONTACT US
Sunday 12th February 2012  © COPYRIGHT 2012 - website design by Website Design by Visualsoft