I recently started a bit of a ruckus on the Minneapolis PerlMongers list by suggesting that Perl prototypes are Good, not Evil. This is going up against the saints of Perl, particularly Tom Christiansen, and so the position was pretty much dismissed out of hand.
I’d like to take this opportunity to say that, after serious consideration, I stand by my position. I will unequivicably state that I think Perl prototypes (with minor reservations) are a Good Thing, and that all new code should use them. After considering his complaints, I will furthermore assert that I think Tom secretly hates Perl, and that he’s blaming prototypes for his fundamental disappointments with the language.
The fundamental issue is whether it is better to allow PI - 1 to secretly and silently become PI(-1), or to allow people to pass arguments as a list — as far as I’m concerned, the issue with the first is bigger than the advantage of the second, and therefore I support prototypes. I believe that if you’re talking about scalars, you should use $, so if you want to pass scalar parameters into a function, you should have to draw scalars out of your array somehow — it’s broken to automagically draw for you.
Now, before continuing I’d want to state that I certainly would agree that there are bugs in prototypes. Here’s the start of a list, although I reserve the right to append onto it:
- The fact that passing an array into a scalar slot is silently converted is certainly a bug — there is the
scalarfunction, it should be used. It should be a warning (at least) to do this in code with thewarningsand/orstrictpragma in play. - The ability to write anything after a
@prototype symbol is a bug. Since the@prototype symbol consumes all remaining elements, the concept of arguments “after” the@is nonsensical. - The existence of the
%prototype symbol is also a bug. The concept of a\%prototype symbol is legitimate (there are hash references), but the concept of a%prototype symbol is nonsense (there is no “hash context”). Perl prototypes force context and silently reference variables, and that’s it. - The nature of the
*and&prototypes need to be made more clear, because they’re awkward and buggy. - Like any new feature of a language, prototypes shouldn’t be retrofitted to code, but should be used in all new code.
Once you get past these (fixable) issues, though, the rest of Tom’s complaints are actually complaints about the Perl language as a whole, in that I can find equivalent problems in the core Perl functions.
Now let’s address Tom’s complaints directly.
The first surprise is that when Perl programmers see “$”, “@”, and “%”, they usually think “scalar”, “array”, and “hash” respectively. This isn’t completely accurate in all cases, but it is, nevertheless, what they often think.
So, in short, Tom is saying that since people haven’t taken the time to really understand what prototypes are, we shouldn’t use them. If you understand that prototypes ONLY force context and magically take references, you’re not going to think “scalar” when you see the “$” prototype: you’re going to think “scalar context”. Assuming that we can make that minor adjustment to the way people think, we gain the major advantage of actually being able to write rand 10, rand 10, rand 10 without it becoming rand(10, rand(10, rand(10))) or something equally nonsensical.
So when the user sees a “prototype” of “$”, the primrose programming path leads them to believe, lamentably, that the compiler will complain if they pass something in that’s not a scalar. Nothing could be further from the truth!
Granted. As stated above, this is a bug: it’s a fixable problem, not built into the design of Perl prototypes. If Tom really doesn’t like it so much, he should hack the Perl code to fix it. Still, even without it being implemented, all that means is that people who use your code wrong are going to get garbage results: GIGO. This problem is also not unique to prototypes (remember Tom hating Perl?): the code length(@arr) will silently and cleanly succeed, which means the issue exists in the core of the language.
@array = (1 .. 10);print length(@array);You might think that would be an error, but it’s not, because there exists an implicit coercion rule for arrays taken in scalar context: it’s the number of elements in that array.
In the particular example of length(@array), non-prototyped code would also gleefully and secretly succeed, returning the length of the first element of the code. Since the same class of problem exists whether or not prototypes are there, it’s not prototypes that are the problem. The problem is the intuitive behavior, but that problem is really with the length function’s name being misleading. Even more, without prototypes, the calls length(), length($a, $b, $c), and length(@arr1, @arr2) would all silently succeed.
There’s more similar arguments laid out, so I’m going to skip over them unless there’s some real insistance. The upshot is that the problems are with Perl’s insistance on trying to do the right thing even when it doesn’t always make sense: the , operator that evaluates expressions and returns the value of the last one, the implicit coercion of lists into scalars, etc., etc. So Tom’s problem isn’t with prototypes, but with Perl’s behavior in those cases. The prototypes are doing precisely what they’re supposed to be doing by forcing context, and when you pass garbage into them, they get you garbage out — the fact that the compiler doesn’t whine is a bug which should be fixed, but not sufficient cause to be rid of them completely.
Let’s examine the “@” “prototype”. What’s that? Is it an array? No, it’s not. It just looks like that. It’s merely a list. Is it a required list? Why no, it’s not. You’re welcome to supply a list of no elements; that is, omit it altogether.
Granted. You need to keep in mind that “@” means “all remaining arguments, possibly zero”. Once you know that’s the case, though, you can code with it. Perl happilly has zero-length lists everywhere else: why does Tom expect prototypes to be different?
Finally, he has this complaint:
To see how this works when you use “\@” in the “prototype”, you haven’t declared a function as taking a reference to an array. Rather, you’ve declared one that takes an array, which the compiler will pass by (implicit) reference to you.
Granted. Perl will take implicit references for you all over the place: for instance, push(@arr1, @arr2), which Tom acknowledges. The fact that you can’t tell that the first is being treated as a reference and the second is being treated as a list isn’t a problem with prototypes, it is a decisison explicitly made to Do The Right Thing while keeping the number of \ characters to a minimum. If you don’t like it, you don’t like that aspect of Perl’s nature, so the problem is much larger than prototypes. Blaming prototypes for them is like blaming a carpenter’s hammer because you don’t like the architecture of your house.
Think of how often you’ve been forced to do something like
push @{ $hash{$string} }, $value;Why can’t you just do this:
push $hash{$string}, $value;It’s because of the “prototype”.
Actually, it’s because Larry Wall didn’t want to have to type push \@arr1, @arr2 every time the push function was used, and because he made the decision that Perl did NOT do implicit dereferencing so that $hash{$string} = $arrref didn’t get silently dereferenced. Again, the problem isn’t the prototype, it’s other aspects of the language that happen to involve calling subroutines.
Given those considerations, I’ve got to say that I just think Tom’s railing against prototypes really exposes a very superficial consideration for how the langauge actually works and how prototypes fit in there — he aggrandizes them to blame them for things that aren’t their fault (the compiler not issuing warnings), and then is annoyed when they don’t do things the language explicitly is designed not to do (implicit dereferencing). It’s not prototypes that Tom has a problem with, it’s Perl.
One Comment
I think I, Mr. Ocaml Advocate, am going to take a contrarian position here, and argue that within the context of perl prototypes are a bad idea.
OK, I’ll give you a minute to pick yourself back up off the floor and for the heart palpitations to die down. Breathe.
I disbelieve in golden hammer languages, the one true language that is the right, or even workable, language for all purposes. This is true for Ocaml- you shouldn’t write an OS in it, nor is it a good language for groking through log files looking for port scanners. It’s true for C- C is also a cruddy language for groking through port scanners, or just about any language where you’re not directly interfacing with hardware. C++ is exception for being the wrong language for any purpose. And it’s true for Perl.
So the question becomes: what is Perl, what is it good for? Perl started life as a performance hack for bourne shell scripts. And, to a large extent, it still is. Perl is a good language for those short scripts to grok through log files, make sure the proper daemons are running, etc.
In this context, prototypes are too heavy weight- at best, they’re meaningless noise, at worst they’re impediments. If the entire program is 100 lines long, there is no advantage to black boxes- you can hold the entire program in your head, as such that is the most efficient way to approach the problem. It’s only when you’re programming in the large that prototypes become usefull, and very shortly thereafter (IMHO) required.
The car analogy applies here- what is the better vehicle, the 18-wheeled semitruck, or the Ford escort? Well, it depends. If what you want to do is to run down to the grocery store for a loaf of bread and a gallon of milk, the escort is a much more practical vehicle. If what you want to do is haul a household’s worth of stuff from one city to another, the semitruck is a much more practical vehicle.
4 Trackbacks
The “Hole in the middle” pattern…
Surfing on Chia’s Functional Longing article, I wanted to post an experience I had recently, working on some C# code. The point of this blog entry is that it’s not what a programming language make possible, it’s what a programming la…
[...] Despite what a certain curmudgeonly ex-coworker of mine might claim, huge Perl scripts are a maintenance nightmare. They’re really a pain. Ranging from surprising overrides of $_ to wrong arguments being passed around to funky results from eval to magical exceptions, the major Perl applications I’ve worked on have had a lot of pain. The best helper to solve this problem is perl -c, use warnings; and use strict; — in other words, bringing more restrictive syntax and typing into the system. Prototypes even help, despite their unpopularity. [...]
[...] occasional visits to dot-Net and OCaml. For a long while, I became enamored with static typing (cite, cite, cite), but through my work with Cornerstone and working with Scala, I’ve had a [...]
[...] If so, then parsing Ruby is equivalent to solving the halting problem. The proof is the same as Perl Cannot Be Parsed: A Formal Proof, with appropriate translation of the code. Notably, this proof is thwarted by the use of perl prototypes (unless you have optional arguments): more on prototypes at A Defense of Prototypes, or, Why Does Tom Christiansen Hate Perl?. [...]