Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

passing subroutine args as a hash: why not?

by Willard B. Trophy (Hermit)
on Jun 05, 2003 at 17:59 UTC ( [id://263426]=perlquestion: print w/replies, xml ) Need Help??

Willard B. Trophy has asked for the wisdom of the Perl Monks concerning the following question:

I'm currently debugging a subroutine that takes exactly 61 arguments. After spending a few days working out how it's called ("... so here argument 37 is null, but over here argument 37 is "Phooey", and argument 53 is null, except when ..."), I'm wondering why more people don't call subroutines with a single hash as the argument.

Is it dreadfully expensive, in terms of memory usage? It's more verbose, true, but it means that:

  • you don't have to specify arguments in any particular order
  • unspecified arguments just don't exist in the argument hash
  • you can see at a glance which values are set to what.

Am I missing something? I can see this would be pointless if there are only a few arguments to be passed, but it looks like it could improve my code legibility no end.

What do monks think? I know that hashes as arguments has come up a few times before (like here, for one), but no-one seems to have expressed whether it's a good idea, a bad idea, or JAWTDI.


For novices who aren't sure what I'm not about, try this:

&foo( bork => 'glorp', ningi => 'fertangg!', flarp => undef ); exit; sub foo(%) { my %args_of = @_; foreach ( keys(%args_of) ) { print "The argument '", $_, "' was set to: ", defined( $args_of{$_} ) ? $args_of{$_} : '(undefined)', "\n"; } 1; }

which outputs:

The argument 'bork' was set to: glorp
The argument 'ningi' was set to: fertangg!
The argument 'flarp' was set to: (undefined)

--

bowling trophy thieves, die!

2003-06-05 edit ybic: <readmore> tags, monasterialized link to related prior thread ( like so [id://120181] ) to prevent logout for monks with cookie not from "www".

Replies are listed 'Best First'.
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

      Your post is correct, but there's no reason you can't 'sprinkle in' positional parameters with a plain hash either. Consider:

      sub sprinkled_in { my $sprinkle1 = shift; my $sprinkle2 = shift; my %unsprinkled = @_; }
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}

      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

        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
      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!

        Why aren't those 61 arguments encapsulated in an easy to use data structure? If they were, your question would be moot. Consider how much improved your life would be if you changed the code so that it things looked like this:

        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.";
        
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.

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; }
      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!

        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.";
        

        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.

•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.

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.

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*

      It would be nice, but I'm on a 5.005 box that would probably die if upgraded.

      --
      bowling trophy thieves, die!

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.

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.
      good point, thanks. Complex structures are likely to get alarmingly broken in transit if you just pass 'em as named parameters. Hash refs fix this.

      --
      bowling trophy thieves, die!

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
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


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.

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}; }
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.

      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
        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.
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!
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.

Re: passing subroutine args as a hash: why not?
by Aristotle (Chancellor) on Jun 07, 2003 at 01:06 UTC
    With something taking 61 parameters, some of them dependent on the values of others, what you should really take a look at is RFC - Parameter Objects.

    Makeshifts last the longest.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://263426]
Approved by halley
Front-paged by arthas
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (4)
As of 2024-03-28 22:41 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found