Friday, July 16, 2010

My Issues With Perl, Redux

I recently had a conversation with a colleague about Perl, and I kind of screwed up my explanation of why Perl drives me nuts. He started talking about Perl's object model, and I tried to explain why Perl doesn't really have one, and things went downhill. I've been thinking about it since then, and my single biggest issue is not with the object model, nor the ugly syntax. No, my issue is with what happens when a given function/method gets called by something.

To explain why this bothers me, we have to compare some code snippets from a couple of languages.

In C: void foo(int bar, int baz, char* hello, char* world, char** argv) {};

In Python: def foo(bar, baz, hello, world, argv): pass

In Perl: sub foo { my ($bar, $baz, $hello, $world, @argv) = @_; }

At first glance, all of these segments appear to be equal to the developer. After all, each of them accepts the same parameters, in the same order, and the same type of parameters being passed in. Neither Perl nor Python have the strong typing that C does, so neither of them actually needs to specify what data type is being passed in at what position. So, all of these look the same.

Take another look at Perl, though. In C and Python, the parameters are given names by the function prototype. I have no code in the body of the function at all. With perl, though, I have to write a line of code to explicitly name these parameters. If I were to rewrite the Perl version to do the exact same function body as the C and Python versions, I would get this line of code:

sub foo {}

Now the difference becomes more apparent. C and Python actually have an explicit list of parameters being sent to the function. Perl doesn't have anything being explicitly passed into it. So, what's the single line of code doing? Why does it matter?

my ($bar, $baz, $hello, $world, @argv) = @_;

In Perl, when a function is called, all of the parameters are pushed into a single list. That list is then made available to function in a specific variable: @_. Now, this can (and does) work well enough most of the time. It does have some gotchas in it, though, and these are where my major issues come from.

Perl will only pass a list of scalars to any given function or method. Nothing else. In Perl, a scalar is a specific, small, value. A number is a scalar. A string is a scalar. A pointer (called references in Perl) is a scalar.

So, what happens if you try to pass a list to a function? All of the elements of that list are appended to the list of parameters. To continue with our code examples above, the following code segments produce the exact same effect in Perl:

foo(10, 20, "hello", "world", (1, 2, 3));

@vals=(10, 20, "hello", "world", (1, 2, 3)); foo(@vals);

And, even more confusing will be this example:

sub foo { my ($hello, $world, @vals)=@_; } foo("hello", "world", (1,2,3), (4,5,6));

The value of @vals in that example is (1,2,3,4,5,6). A single list, containing all of the elements of both lists at the end of the call. If you are using a hash (associative array), it gets even more fun:

sub foo { my ($hello, $world, @vals)=@_; } foo("hello", "world", ('1' => '2', '3' => '4'));

@vals is now just a list instead of the hash that was passed in. Depending on the order Perl adds to the stack, the list could be either (1,2,3,4) or (3,4,1,2).

Okay, one last example to show how messed up this is:

sub foo { my (%vals)=@_; } foo("hello", "world", ('1', '2', '3', '4'));

Now, all of the parameters that were passed in have been munged into a single hash. The keys of that hash are "hello", "1", and "3".

Yes, Perl can be made to work. People have done so, and have even done so with very large projects. That does not mean it is a good programming language. That does not mean it is easy. Nor does it mean it is an easily maintained programming language. With the issues that I have shown in passing parameters around to functions/subroutines/methods, I can think of only one other programming language that makes it so difficult to be consistent in how things get done, and that's assembly.

I found myself tempted to add a derogatory remark, but I don't think that's really necessary. When even such a (usually) disliked language as C manages to get something this basic correct, and Perl doesn't, I think enough has been said without adding the snark.


Keith said...

you're not *quite* as 'the same' with Perl as the others. It's a little weaker than Python's version, but

sub foo($$$$@) { my ($bar, $baz, $hello, $world, @argv) = @_; }

be rather closer.

Michael Pedersen said...

That's a fair enough statement, up to a point.

I ran another quick test. Using, I just changed to add the ($$$$@) after the "sub foo".

The results were the same. While it is possible to use such prototypes, they seem to be rarely used in practice (I, personally, have never seen them used). People like Tom Christiansen hate Perl prototypes, and even people who defend them seem to have issues with them.

Are the lines precisely identical between the three languages? No, and they cannot be. I showed why over the course of this post. So, up to a point, your comment is a fair one.