|There's more than one way to do things|
I recently had cause to resurrect my sermon against inappropriate use of Perl prototypes at a Perl Mongers meeting. I thought it might be of interest to some here too.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:
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:
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:
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:
only tells us that it expects three arguments, whereas a subroutine that starts like this:
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 probably 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 prototype 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. So it doesn't.
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:
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:
it will return "border: 1px solid black;".
But if we call it like this:
Then if $selected is true, we might expect this return value:
but we'd actually get:
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) leaving the second and third arguments to default.
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, we never got to 7 the last time I had this discussion in real life :-)
Since writing the above, I've encountered a coder who insists on using the perl4-style & prefix on subroutine calls - on the grounds that it clearly delineates subroutines from builtin functions (a questionable argument at best). Be warned that Perl does not check prototypes when a subroutine call includes the & prefix.