Recipe 10.11 Prototyping Functions

10.11.1 Problem

You want to use function prototypes so the compiler can check your argument types.

10.11.2 Solution

Perl has something of a prototype facility, but it isn't what you're thinking. Perl's function prototypes are more like a context coercion used to write functions that behave like some Perl built-ins, such as push and pop.

10.11.3 Discussion

Manually checking the validity of a function's arguments can't happen until runtime. If you make sure the function is declared before it is used, you can tickle the compiler into using a very limited form of prototype checking. But don't confuse Perl's function prototypes with those found in any other language.

A Perl function prototype is zero or more spaces, backslashes, or type characters enclosed in parentheses after the subroutine definition or name. A backslashed type symbol means that the argument is passed by reference, and the argument in that position must start with that type character.

A prototype can impose context on the prototyped function's arguments. This is done when Perl compiles your program. But this does not always mean that Perl checks the number or type of arguments; since a scalar prototype is like inserting a scalar in front of just one argument, sometimes an implicit conversion occurs instead. For example, if Perl sees func(3, 5) for a function prototyped as sub func ($), it will stop with a compile-time error. But if it sees func(@array) with the same prototype, it will merely put @array into scalar context instead of complaining that you passed an array, but it wanted a scalar.

This is so important that it bears repeating: don't use Perl prototypes expecting the compiler to check type and number of arguments for you. It does a little bit of that, sometimes, but mostly it's about helping you type less, and sometimes to emulate the calling and parsing conventions of built-in functions.

10.11.3.1 Omitting parentheses

Ordinarily your subroutines take a list of arguments, and you can omit parentheses on the function call if the compiler has already seen a declaration or definition for that function:

@results = reverse myfunc 3, 5;

Without prototypes, this is the same as:

@results = reverse(myfunc(3, 5));

Without parentheses, Perl puts the righthand side of the subroutine call into list context. You can use prototypes to change this behavior. Here is a function that's prototyped to take just one argument:

sub myfunc($); @results = reverse myfunc 3, 5;

Now this is the same as:

@results = reverse(myfunc(3), 5);

Notice how the scalar prototype has altered the Perl parser! It grabs only the next thing it sees, leaving what remains for whatever other function is looking for arguments.

A void prototype like:

sub myfunc( );

will also alter the parser, causing no arguments to be passed to the function. This works just like the time built-in.

That means that in the absence of parentheses, you cannot know what is going on by casual inspection. Things that look the same can quietly behave completely differently from one another. Consider these declarations and assignments:

sub fn0( ); sub fn1($); sub fnN(@); $x = fn0 + 42;       $x = fn1 + 42;       $y = fnN fn1 + 42, fn0 + 42; $y = fnN fn0 + 42, fn1 + 42; $z = fn1 fn1 + 42, fn1 + 42; $z = fnN fnN + 42, fnN + 42;

Astonishingly enough, those are parsed by the Perl compiler as though they'd been written this way:

$x = fn0( ) + 42; $x = fn1(42); $y = fnN(fn1(42), fn0( ) + 42); $y = fnN(fn0( ) + 42, fn1(42)); $z = fn1(fn1(42)), fn1(42);   $z = fnN(fnN(42, fnN(42)));

Without first looking closely at the prototypes and then thinking really hard about how Perl's parser works, you'd never be able to predict that. Maintainability would suffer horribly.

This is one strong argument for using more parentheses than might be demanded by purely precedential concerns (or, alternatively, this is an argument for avoiding prototypes).

10.11.3.2 Mimicking built-ins

The other common use of prototypes is to give the convenient pass-without-flattening behavior of built-in functions like push and shift. When you call push as push(@array, 1, 2, 3) the function gets a reference to @array instead of the actual array. This is accomplished by backslashing the @ character in the prototype:

sub mypush (\@@) {   my $array_ref = shift;   my @remainder = @_;   # ... }

The \@ in the prototype says "require the first argument to begin with an @ character, and pass it by reference." The second @ says "the rest of the arguments are a (possibly empty) list." A backslash in a prototype requires that the argument actually begin with the literal type character, which can sometimes be annoying. You can't even use the conditional ?: construct to pick which array to pass:

mypush( $x > 10 ? @a : @b, 3, 5 );           # WRONG

Instead, you must play games with references:

mypush( @{ $x > 10 ? \@a : \@b }, 3, 5 );    # RIGHT (but ugly)

Here's an hpush function that works like push, but on hashes. It uses a list of key-value pairs to add to an existing hash, overwriting any previous values associated with those keys.

sub hpush(\%@) {     my $href = shift;     while ( my ($k, $v) = splice(@_, 0, 2) ) {         $href->{$k} = $v;     }  }  hpush(%pieces, "queen" => 9, "rook" => 5);

You may also backslash several argument types simultaneously by using the \[ ] notation:

sub mytie ( \[$@%&*] $; @ )

That function accepts any of the five types and passes it by reference, followed by one mandatory scalar context argument and optional trailing list of remaining arguments.

You can discover a particular function's prototype using the prototype built-in function. For example, calling prototype("hpush") given the previous definition would return the string "\%@". You can even find out a built-in's prototype this way if it has one, that is. Not all core built-ins can be emulated. For those that can, the prototype function returns what their built-in prototype is. Since you can always call a core built-in function like int as CORE::int, built-ins are deemed to reside in package CORE. For example:

for $func (qw/int reverse keys push open print/) {      printf "Prototype for %s is %s\n", $func,          prototype("CORE::$func") || "UNAVAILABLE"; } Prototype for int is ;$ Prototype for reverse is @ Prototype for keys is \% Prototype for push is \@@ Prototype for open is *;$@ Prototype for print is UNAVAILABLE

10.11.4 See Also

The prototype function in perlfunc(1); the section on "Prototypes" in Chapter 6 of Programming Perl and in perlsub(1); Recipe 10.5



Perl Cookbook
Perl Cookbook, Second Edition
ISBN: 0596003137
EAN: 2147483647
Year: 2003
Pages: 501

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net