Re: passing subroutine args as a hash: why not?
by hardburn (Abbot) on Jun 05, 2003 at 18:16 UTC
|
I generally prefer a hash ref, because I find it easier to work with.
sub hash
{
my %in = @_;
. . .
}
sub hashref
{
my $in = shift;
my %in = %{ $in };
. . .
}
hash( foo => 'bar' );
hashref({ foo => 'bar' });
At first glance, it looks like there is slightly more code to do a hashref compared to a plain hash. However, IIRC, mis-formatting the hash in the subroutine call will be a run-time error, whereas mis-formatting the hashref will be compile-time. IMHO, if you get to choose, compile-time errors are better than run-time errors. Additionally, hashrefs let you sprinkle in a few positional params, if you so choose.
sub hashref_and_positional
{
my $param1 = shift;
my $param2 = shift;
my $in = shift;
my %in = %{ $in };
. . .
}
hashref_and_positional('foo', 'bar', { baz => 'blah' });
---- I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
-- Schemer
Note: All code is untested, unless otherwise stated
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
sub sprinkled_in {
my $sprinkle1 = shift;
my $sprinkle2 = shift;
my %unsprinkled = @_;
}
| [reply] [Watch: Dir/Any] [d/l] |
Re: passing subroutine args as a hash: why not?
by thelenm (Vicar) on Jun 05, 2003 at 18:09 UTC
|
I'm wondering why more people don't call subroutines with a single hash as the argument.
Most people don't write subroutines that take 61 arguments! :-) I've gotta wonder what that subroutine is doing, and whether it may be better to break it up into several subroutines that each do something small and specific.
That said, when you do have a fairly large number of subroutine arguments, I think passing them as named parameters in a hash is a fine idea, for the reasons you mentioned.
-- Mike
--
just,my${.02}
| [reply] [Watch: Dir/Any] |
|
For your typical sub, there is a great problem with that many params. However, I can see how a perfectly good object would have a constructor with quite a lot of args. Perhaps not 61, but they often have a lot more than a typical sub (just look at all the options in the HTML::Template constructor).
---- I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
-- Schemer
Note: All code is untested, unless otherwise stated
| [reply] [Watch: Dir/Any] |
|
Actually, I'd say the paucity of arguments is just as important in constructors as in subroutines and for reasons beyond the mere fact that in Perl a constructor just is a class method. For one thing, any object that takes more than a couple of seconds of consideration as to how it may be instantiated will discourage its own use. In either case it indicates generally that insufficient thought was given to breaking up responsibilies among constituent parts. Not always, but often the first thing to consider if you've got vast numbers of arguments to a constructor is the use of inheritance to encapsulate the differences among data and behavior normally conveyed via parameters.
Consider, say, a Logger class with multiple different types of logging available, e.g., types of warning raised if any, location of error logs, email or page contacts, etc. Better to create a CarpLogger subclass that inherits behavior and already knows what to do than to clutter Logger with if/else code deciding actions based upon arguments. A smaller, simpler constructor improves maintainability and usability.
"The dead do not recognize context" -- Kai, Lexx
| [reply] [Watch: Dir/Any] |
|
|
|
The 61-headed 350+ line monster sub is taking all the registration information from a huge web form, and storing it in several tables in a database. I think it's all lumped together so the db can be rolled back if something goes wrong.
I wouldn't have done it that way had it been my code. But I'm just the maintainer, and I have to keep my new named-parameter routine in line with the old one until we're sure everything works.
--
bowling trophy thieves, die!
| [reply] [Watch: Dir/Any] |
|
my $query = CGI->new;
my %reg_info = parse_registration_info($query);
my $success = store_registration_info(\%reg_info, $database_handle);
or, alternatively, like this:
my $query = CGI->new;
my $registration = OurWebsite::User::Registration->new($query);
my $success = $registration->store($database_handle);
Sure, that's contrived. But the point is that passing around dozens of arguments is ridiculous. If you changed the function to take named arguments, you'd be passing twice as many arguments total (the args and their names.) Working with that much cruft is neither easy nor necessary. Build a data structure that holds that information once, as you parse it, and then pass around a single reference to that structure.
-sauoq
"My two cents aren't worth a dime.";
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
Re: passing subroutine args as a hash: why not?
by arthas (Hermit) on Jun 05, 2003 at 18:03 UTC
|
"Named parameters" are always a good idea if you have a lot of parameters, I agree with you on that. That said, many people already use them: have a look at modules such as CGI.
Michele.
| [reply] [Watch: Dir/Any] |
Re: passing subroutine args as a hash: why not?
by chromatic (Archbishop) on Jun 05, 2003 at 18:23 UTC
|
Nit: you don't need the prototype; Perl does the right thing for you automatically.
I like hashes in constructors where I may have to set defaults. They're also nice when you don't want to remember the positions of arguments:
my $alias = Mail::SimpleList::Alias->new(
Expires => '7d',
Auto_add => 0,
Closed => 1,
);
# and in Mail::SimpleList::Alias
sub new
{
my $class = shift;
bless {
Owner => '',
Auto_add => 1,
Expires => 0,
Closed => 0,
@_;
}, $class;
}
| [reply] [Watch: Dir/Any] [d/l] |
|
I know I don't need the % prototype, but it helps those who come after me know what the subroutine expects.
I'm sure you didn't use the term nit in the way it means where I'm from ...
--
bowling trophy thieves, die!
| [reply] [Watch: Dir/Any] |
|
I know I don't need the % prototype, but it helps those who come after me know what the subroutine expects.
That's no reason to use a prototype. That's what comments are for. Perl's prototypes aren't really prototypes at all. It's a great irony that so many people use them under the impression that they are making their code more maintainable because using them without knowing exactly when it is OK to use them will do exactly the opposite.
Prototypes in Perl should be avoided in all but the rarest of cases. See Tom Christiansen's article on them.
-sauoq
"My two cents aren't worth a dime.";
| [reply] [Watch: Dir/Any] |
|
|
|
I meant nit as in nitpick, which appears to have an etymology related to lice eggs. (Those little circles in the percent sign are a happy accident.)
I don't understand what you mean by it helps those who come after me know what the subroutine expects. If they're reading the code, why not read the very next line in the function to see exactly how you're dealing with parameters? If they're reading the documentation, you'll have to tell them what to expect anyway.
I'm all for not repeating myself. I just don't see what the function parameter adds.
| [reply] [Watch: Dir/Any] |
•Re: passing subroutine args as a hash: why not?
by merlyn (Sage) on Jun 05, 2003 at 19:27 UTC
|
I've never seen a subroutine with 61 arguments in well designed or maintainable code.
Either some of those arguments are logically related, and therefore really a single argument with attributes, or this is going to be a very difficult subroutine to develop, test, and maintain.
61 arguments. I can think of 60 arguments why not to do that. {grin}
-- Randal L. Schwartz, Perl hacker
Be sure to read my standard disclaimer if this is a reply. | [reply] [Watch: Dir/Any] |
Re: passing subroutine args as a hash: why not?
by perrin (Chancellor) on Jun 05, 2003 at 18:19 UTC
|
Named parameters (passing a hash of params to subs) were part of the coding standards at one place where I worked. They are generally considered a good thing.
Subroutines that take 61 parameters are generally considered a bad thing. The person who wrote that sub needs to learn some basic principles of good programming. | [reply] [Watch: Dir/Any] |
Re: passing subroutine args as a hash: why not?
by particle (Vicar) on Jun 05, 2003 at 18:52 UTC
|
i've found named parameters extremely helpful. in fact, perl 5.008 has made them even better, thanks to locked hashes (the pseudo-hash replacement.) enter Hash::Util...
#/usr/bin/perl
use strict;
use warnings;
$|++;
require 5.008;
use Hash::Util;
my %hash= (
one => undef,
two => 2,
three => 'abc',
);
Hash::Util::lock_keys( %hash );
print $_,$/ for keys %hash;
## causes runtime error
$hash{add}= 'yes';
now i can both provide default values in my modules or subroutines, and automatically handle errors when extraneous parameters are passed. also, specific keys can be locked, so you can create a hash of constants.
that allows me to delete all my code like
defined $hash{$key}
or die $key, ' is not valid';
because Hash::Util::lock_hash handles it for me! aah, Hash::Util.
~Particle *accelerates*
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
| [reply] [Watch: Dir/Any] |
Re: passing subroutine args as a hash: why not?
by dws (Chancellor) on Jun 05, 2003 at 20:32 UTC
|
I'm currently debugging a subroutine that takes exactly 61 arguments. After spending a few days working out how it's called ... Is it dreadfully expensive, in terms of memory usage?
A subroutine with 61 arguments is dreadfully expensive in terms of people time! Just think of the testing. The combination of tests cases you need to even partially cover such a beast is outrageous. And if everyone who needs to use the thing has to spend several days puzzling out the API, that's a huge hit.
Don't look at this as an excuse to pass a hash, look at it as an opportunity to refactor the subroutine.
The only time I've seen something with anywhere near that many parameters was in old (non-OO) FORTRAN code. I'll bet that many of those 61 parameters represent aspects of objects. Discover and create the right objects, and a good percentage of those parameters go away. And you can unit test the objects individually. Verifying that the internal states of several objects are consistent is much easier than flattening out and combining their internal representations, and then verifying that the resulting pile of values is in a consistent state.
Chances are also good that your subroutine is trying to do too much, but without seeing code, that's hard to tell.
| [reply] [Watch: Dir/Any] |
Re: passing subroutine args as a hash: why not?
by hmerrill (Friar) on Jun 05, 2003 at 20:18 UTC
|
I, like other responders, like to pass hash *references* as opposed to hashes. This comes in handy especially when you want to pass other things, as well as a hash, as arguments to a subroutine. Hashes and arrays get flattened out into one long string of scalar parameters, so if you stick to passing scalars (like hash references) around, you'll never have problems :-)
Here's a snippet from 'perldoc perlsub':
The Perl model for function call and return values is simple: a
+ll func-
tions are passed as parameters one single flat list of scalars,
+ and all
functions likewise return to their caller one single flat list
+of
scalars. Any arrays or hashes in these call and return lists w
+ill col-
lapse, losing their identities--but you may always use pass-by-
+refer-
ence instead to avoid this. Both call and return lists may con
+tain as
many or as few scalar elements as you’d like. (Often a functio
+n with-
out an explicit return statement is called a subroutine, but th
+ere’s
really no difference from Perl’s perspective.)
If you instead pass parameters like this:
sub print_person {
my $address_hashref = shift;
my $hobbies_arrayref = shift;
print "address line 1 = $address_hashref->{'addr1'}\n";
print "address line 2 = $address_hashref->{'addr2'}\n";
# etc, etc.
# or, turn hashref back into a hash, and arrayref back into an
+array
my %address = %{$address_hashref};
my @hobbies = @{$hobbies_arrayref};
print "address line 1 = $address{'addr1'}\n";
}
my %address = (
"addr1" => "1 Main St",
"addr2" => "Suite 101",
"city" => "Somecity",
"state" => "Somestate"
);
my @hobbies = ( "flying", "mountain climbing" );
print_person(\%address, \@hobbies);
HTH. | [reply] [Watch: Dir/Any] [d/l] [select] |
|
| [reply] [Watch: Dir/Any] |
Re: passing subroutine args as a hash: why not?
by djantzen (Priest) on Jun 05, 2003 at 18:28 UTC
|
Named parameters have a sliding value based upon how variable they are -- that is, mandatory or not -- and how many you have. In your case, since some of them may be undef and since you have a number well beyond reason, you'll benefit from using them. However, any function that takes 61 arguments -- heck, that takes more than 6 -- probably indicates poor design. That's like including in the instructions for making coffee 1) be born poor in the Southern Hemisphere; 2) go to school for two years; 3) begin picking beans for Starbucks to support your family; 4 -10) ....; 11) profit!!!; 12) go the store to buy a pound of premium coffee beans .... I would love to see an outline of what in the world this thing is doing with 61 arguments. I'm sure it can be refactored.
As far as the expense of hashtables goes, first compare your emotional pain under the current system with what you envision given named parameters. Next, note that in your sample code you're not actually constructing a hash as a parameter, named or anonymous. Rather, you're building a list that then is used inside the sub to populate a hash. So if you're worried about unnecessarily creating intermediate objects, you should be okay.
"The dead do not recognize context" -- Kai, Lexx
| [reply] [Watch: Dir/Any] |
Re: passing subroutine args as a hash: why not?
by BrowserUk (Patriarch) on Jun 05, 2003 at 22:24 UTC
|
Seen as 61 arguments, the idea sounds more than faintly ludicrous. Seen as a function that processes a record containing 61 fields, maybe less so, provided that the hash is built automatically by some prior piece of code. If the coder has to build the hash either in-line, or specifically for the purposes of calling the sub, then it is ludicrous and should be refactored.
Maintaining such a record internally in the form of a hash mkes perfect sense. Passing the hash to the subroutine that processes it doesn't. Pass a reference to the hash. What waste all those cycles flattening a nicely structured hash into a list only to rebuild the list into a hash inside the sub?
Examine what is said, not who speaks.
"Efficiency is intelligent laziness." -David Dunham
"When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller
| [reply] [Watch: Dir/Any] |
Re: passing subroutine args as a hash: why not?
by msemtd (Scribe) on Jun 06, 2003 at 08:22 UTC
|
I'm wondering why more people don't call subroutines with a single hash as the argument.
I guess more people don't do it because they don't usually have so many arguments to pass!
In the world of Perl/Tk its pretty standard to pass a hash of args since there's so many that are optional, inherited, etc.
| [reply] [Watch: Dir/Any] |
Re: passing subroutine args as a hash: why not?
by IOrdy (Friar) on Jun 06, 2003 at 00:49 UTC
|
I dont know if this is a good idea or not but I have taken to testing for a hash or hashref with something like the following:
#!/usr/bin/perl -w
use strict;
my %foo = (
a => 'b',
b => 'c',
);
print hash_or_hashref(%foo);
print hash_or_hashref(\%foo);
sub hash_or_hashref {
die "missing options" unless $_[0];
# was it a hash or hashref passed.
my $options;
if (UNIVERSAL::isa($_[0], 'HASH')) {
$options = shift;
} else {
$options = {@_};
}
return $options->{a};
}
| [reply] [Watch: Dir/Any] [d/l] |
Re: passing subroutine args as a hash: why not?
by Anonymous Monk on Jun 05, 2003 at 19:26 UTC
|
I find them to be an atrosity. Forget the number of parameters, but what you are passing around.
If one key gets misspelled, it can create an ugly bug that is hard to find. Pass in your parameters like a normal hu-man. And if you can use OOP, dont' pass them in as hashes either. Create attributes where they make sense, pass in the rest. | [reply] [Watch: Dir/Any] |
|
If one key gets misspelled, it can create an ugly bug that is hard to find.
This is one of the best reasons to use named parameters! Within your subroutine you can match the names you received against a list of names you expected to receive and you know immediately what's missing. You can also wrap the arguments in a temporary parameter object that automatically does the argument checking for you.
"The dead do not recognize context" -- Kai, Lexx
| [reply] [Watch: Dir/Any] |
|
So now you have to add logic to figure out what was misspelled in each subroutine. Talk about overhead.
sub somethingComplicated()
{
my( $var1, $var2, $var3, $var4 ) = @_;
}
Calling this and screwing up the assignment to var3 via the function call is quite hard. It's called protecting the developer from shooting himself in the foot. | [reply] [Watch: Dir/Any] [d/l] |
Re: passing subroutine args as a hash: why not?
by talexb (Chancellor) on Jun 06, 2003 at 19:19 UTC
|
Use a hashref, as others have suggested. When you do, I can highly recommend Params::Validate for checking that certain hashref parameters a) exist b) are of the right type. And 61 data items is a huge number -- IMO, anything more than three is asking for trouble.
I know, do what you gotta do, but in future, use a hashref.
--t. alex
Life is short: get busy!
| [reply] [Watch: Dir/Any] |
Re: passing subroutine args as a hash: why not?
by Oberon (Monk) on Jun 07, 2003 at 00:31 UTC
|
As an aside, am I committing the sin of premature optimization if I worry that passing as a hash is less efficient than passing as a hashref? I mean, doesn't this:
my %params =
(
param1 => 'thing',
param2 => 'other thing',
# bunches o' stuff
param61 => 'whew!',
);
initialize_object(%params);
# later ...
sub initialize_object
{
my %params = @_;
# do something useful, presumably
}
actually copy all 61 parameters not once but twice? As opposed to:
my $params =
{
param1 => 'thing',
param2 => 'other thing',
# bunches o' stuff
param61 => 'whew!',
};
initialize_object($params);
# later ...
sub initialize_object
{
my ($params) = @_;
# do something useful, presumably
}
? I noticed some people preferred hashref's to hashes, but no one mentioned this as a reason ... is the overhead too little to fret about really?
Just curious. | [reply] [Watch: Dir/Any] [d/l] [select] |
Re: passing subroutine args as a hash: why not?
by Aristotle (Chancellor) on Jun 07, 2003 at 01:06 UTC
|
| [reply] [Watch: Dir/Any] |