Phil Tomson wrote:
Actually, I think this is one of the _worst_ things about Perl and it was one of those nagging little annoyances that when added up finally led me to seek out alternatives. The problem with the way parameters are passed to subroutines in Perl is that you can't tell from a glance how many params are supposed to be passed into the subroutine and you have no information about what the params are. You've got to look down through the subroutine code to see if there's a 'shift @_' lurking somewhere in the code (for the uninitiated, you pass params to Perl subroutines via the '@_' global list; you have to 'manually' shift the params off of that list - blech! )
An incredibly interesting point of view though your justification for it is somewhat understandable. Pathological mistakes can occur this way for those that use this, what I would call an incredibly nice feature, in an undisciplined way. I'm quite disciplined about spiking out my arguments in Perl at the top of a subroutine.
Did you do this in comments? In my faint recollections of Perl I seem to recall that there was a way to declare the sub with it's params - but most people didn't do this (or perhaps my memory is incorrect).
No, I usually spike them out at the top of the subroutine. Like this:
sub foo {
## Get parameters.
my $bar = shift; ## Comment variable use here
my $boat = shift; ## Same here
## Return bar and boat concat'd.
"$bar$boat";
}
In general, that's how ALL my Perl subroutines look. It IS a disciplined approach and I guess I have to say that by doing so I am implying some rightness in other people's comments here because I dislike the same loosy-goosy approach in what I call "trash code" that other people write in Perl. You look at their Perl code, and you can't make hide nor hair of it without sitting down, marking out variables, etc. as some have stated.
I guess I'm willing to live with the power at the expense of the potential for a mess, but it can get messy when it ends up that there are 50 ways to "pass" the variable(s) in. Some of which I dislike immensely myself such as this approach:
sub foo {
my( $bar, $boat ) = @_;
"$bar$boat";
}
Still, sometimes those different approaches make sense. In OO modules, I've been known to do, assuming foo is a method I am defining in an OO module, the following:
sub foo { "$_[0]$_[1]" }
When I see that, because of all the Perl coding I've done, I know IMMEDIATELY what's going on. It appears, to my surprise in this NG (though I'd never really thought about it much until now) that some people don't like this.
Generally, just because of the way I am "wired" if a language is somewhat regimented, I like that and can go with it (I recall earlier days in Pascal, and I considered C somewhat that way as well), and if the language is "loosy goosy" like Perl, I super-impose my own regimented, methodical way of coding. I think mainly because I've found that consistency in approach (to the nth detail) leads to robustness and maintainability. And how much time does it take to spike out my variables as I demo'd above versus just slapping them around somewhere in the body of the subroutine? Or indenting consistently throughout, etc.? It doesn't take much, and yet there are A LOT of people that don't, so the comments here have some merit, I do admit.
Incidentally, perhaps the thing you are referring to in Perl, denoting parameters is the following notation:
sub foo($$) {
## Body of subroutine here.
}
This denotes two parameters are intended. I'm not overly familiar with this, and I don't use it. It still doesn't spike out parameters as you all here are advocating.
Essentially what you're trying to get is to be able to pass in a variable number of arguments to a method (err, function in Perlese). Ruby lets you do that like so:
def foo(*args)
At least in this case you can tell at a glance that foo can take a variable number of arguments.
In perl you could have:
sub foo {
for(@_) {... do whatever...}
}
In the case of Perl, I've got to actually go and read the body of the function to know that it can take multiple arguments. I don't like it.
But the Perl construct you have above is powerful in certain situations just as you indicate later here concerning Ruby and the block/yield methodology. Only in Ruby you have the ability to make it more readable with the foo.call method as you pointed out. But in Perl, the following is extremely powerful used in the right context:
sub puts { for (@_) { print "$_\n" } }
Because the intent is to use all parameters passed until all have been iterated over. Unfortunately, that same @_ notation can be used in ways I don't like too. So, I guess I have to say, I do understand somewhat.
The other thing I don't like about it is that it means that argument handling in Perl is a do-it-yourself project (like OO Perl)
<Sigh> Yep. Which is one of the reasons Ruby is appealing to me. It's A LOT OF STINKING WORK to create even the simplest Perl OO module, I have to admit. Versus:
class Foo
attr_reader :bar, :boat
def initialize( bar, boat )
@bar, @boat = bar, boat
end
def concat
@bar + @boat
end
end
foo = Foo.new
p foo.concat
p foo.bar
p foo.boat
Wow. And you have accessors and everything right there. In Perl, the above is easily 10-20 lines of code and the less lines, the more ambigious it would be. The above Ruby is just plain nice.
And to bring this Perl discussion back on topic, in line with your dislike of this "feature" in Perl, am I not correct in understanding that in Ruby, one can create methods on the fly in a class? How is this that much different (conceptually) than unspecified parameters in a Perl subroutine?
It could present similar problems, however, creating methods on the fly, while possible, it's relatively unusual compared to what we're talking about in Perl. Every sub in Perl requires that I read the body of the sub to determine what the args are and how many. I would also contend that the 'problem' of creating methods on the fly is not as big because what is happening is that you're getting some new feature (a new method) created on the fly (or perhaps you're overriding an old one) that you may not be aware of - not knowing about a new method won't break anything because you probably won't call what you don't know about.
OK, my question was from the vantage of not knowing for sure, so this explains that wonderment.
How do you know what methods are there (aside from using the inspection methods available to every class [unless I am mistaken in this regard]).
I'll give you a better example for your the point you're trying to make: yield.
In Ruby you can have:
def foo
#...
yield
#...
end
Now there is no indication from looking at the method definition line (the 'def foo' part) that any argument is passed to this method, yet it requires that a block be passed to it, like so:
foo { puts "Boo!" }
So, it's a bit like the problem I'm talking about with Perl, except that in practice it's not as bad because usually there is only one yield in a method - but it can be an issue. That's why it's probably better to do:
def foo(&b)
#...
b.call
#...
end
Because it provides the reader of the code with a clear indication that the method takes a block. Also, it's usually a good idea if you do use yield to also call 'block_given?' prior to yielding.
Yes, this is a better example of what I was talking about, and yes, you can provide better indication here in Ruby than in Perl, I have to admit. So, to see the parallels, I'll bet you see a lot more of the simple yields in block handling method definitions in classes than you do in the form of you second example with the spiked out parameter and the param.call approach. It's disciplined, well constructed code, but not everyone is like that. Hence the Perl dilemma/diatribe here.
-ceo