[Wellington-pm] Gratuitous use of Perl Prototypes

Grant McLean grant at mclean.net.nz
Mon Nov 8 16:06:27 CST 2004


Following on from my comments last night, I present the case against
inappropriate use of Perl prototypes.

But first, let's start with the case *for* appropriate use of Perl 
prototypes.  Imagine that you wanted to write a function that wrapped
Perl's builtin function 'push' but forced everything being pushed onto 
the array to upper case.  Your starting position might be this:

  sub PUSH {
    my(@array, @new_items) = @_;

    push(@array, map { uc } @new_items);
  }

  my @target = ('RED', 'GREEN');
  PUSH(@target, 'blue');

This obviously won't work since all arguments passed to PUSH() will end
up in @array and @new_items will end up empty.  Another reason it won't
work is that the push function is operating on @array which only exists
within the PUSH function and doesn't affect the original array
(@target).

One way to make it work is to pass in an array reference instead of an
array:

  sub PUSH {
    my($array, @new_items) = @_;

    push(@$array, map { uc } @new_items);
  }

  my @target = ('RED', 'GREEN');
  PUSH(\@target, 'blue');

This is fine, but it does require a little more work from the caller
(actually one backslash) and it's not quite as tidy as Perl's builtin
'push'.

Prototypes allow your functions to behave like Perl's builtins.  In
our example, we can use a prototype to declare that our PUSH function
needs one arrayref, followed by an arbitrarily long list of arguments:

  sub PUSH (\@@) {
    my($array, @new_items) = @_;

    push(@$array, map { uc } @new_items);
  }

  my @target = ('RED', 'GREEN');
  PUSH @target, 'blue';

Now when we call PUSH, we specify an array as the first argument and
Perl coerces that into the arrayref that PUSH requires.

Even though this is what Perl prototypes are intended to be used for,
it could be argued that even this use is a bad thing, since it might
lead to unexpected consequences.  Usually in Perl if you pass an array
to a subroutine, you don't expect the array to be changed.  If a 
subroutine expects an arrayref, then that might be taken as a signal
that the routine possibly intends to modify the referenced array.
Prototypes allow a programmer to hide the fact that the routine will
get an arrayref.


Now let's open the case for the prosecution.  Let's assume that we have
a persistent whiney developer who is determined to use prototypes on
every Perl subroutine and is attempting to defend his choice:

1. Prototypes allow Perl to pick up errors in subroutine calls.

   This is a common mistake made by developers who have a background
   with 'C', since this is exactly what 'C' prototypes are for.
   It is unfortunate that Perl uses the name 'prototype', since in
   Perl, the feature exists for an entirely different reason - to
   allow Perl to silently coerce the arguments into a form that 
   matches the prototype.  In fact Perl's compiler will only throw
   errors if it can't manage to do that.

2. Prototypes are a useful way of documenting a subroutine's
expectations.

   Perhaps, but a subroutine that starts like this:

     sub border_style ($$$) {

   only tells us that it expects three arguments, whereas a subroutine
   that starts like this:

     sub border_style {
       my($width, $style, $colour) = @_;

   tells us not only that the subroutine expects three arguments, but
   also how it is planning to interpret each argument.

   Since you're probably going to assign your arguments to variables
   with meaningful names anyway, the prototype is essentially redundant.
   If you need more documentation, then POD is the right tool for the
   job.

3. Well at least I know my methods are being called with the right 
   number of arguments.

   Whoa there!  Did you say 'methods'?  Perl does not attempt to
   check prototypes at all for subroutines called as methods.
   Remember, if your classes use inheritance, a method could be
   defined in more than one place in the inheritance tree and it's
   entirely possible that the calling signature for each might be
   different.  Since Perl doesn't know until runtime which subroutine
   implements a method for a specific object, it can't check the 
   prototypes at compile time.

4. OK, so prototypes are not perfect, but at least they pick up some
   classes of errors at compile time.

   Perhaps, but used gratuitously, they can actually introduce some
   errors at runtime.  Consider this subroutine:

    sub border ($;$$) {
      my($width, $style, $colour) = @_;

      $style  = 'solid' unless defined $style;
      $colour = 'black' unless defined $colour;

      return "border: $width $style $colour;";
    }

   In theory, the prototype declares that the subroutine expects
   one mandatory argument and two optional arguments (for which
   the routine defines default values).  So if we call it like
   this:

    print border('1px'), "\n";

   it will return "border: 1px solid black;".

   But if we call it like this:

    my @args;
    push @args, $selected ? '5px' : '1px';
    push @args, 'dashed';
    push @args, 'red' if $selected;

    print border(@args), "\n";

   Then if $selected is true, we might expect this return value:

    "border: 5px dashed red;"

   but we'd actually get:

    "border: 3 solid black;"

   The reason for this is that the prototype tells Perl we want the
   first argument as a scalar, so it evaluates @args in a scalar
   context which gives 3 (the number of elements in @args) and calls
   the subroutine with the lone argument (3).

5. Well the coding standards for my project mandate the use of
prototypes

   That's a bug in the coding standards.  It might be correct for 
   other languages, but it's not correct for Perl.

6. My use of prototypes sends a clear message to people reading my code
   that I am a careful coder.

   You're right, it does send a clear message, just not the one you
   think.  People reading your code are pointing and sniggering as we 
   speak.

7. Alright you win.

   Actually, if you stop the gratuitous use of prototypes, we all win.

In truth, the last time I went through this sermon we never got to
step 7 :-)

Cheers
Grant



More information about the Wellington-pm mailing list