http://qs321.pair.com?node_id=120181

c has asked for the wisdom of the Perl Monks concerning the following question:

Currently, i am using the following to pass a group of directives into a subroutine for use:

(my $host, my $source, my $destination) = @_;

however with a recent post i've made, i've come to some realization that it will be significantly much easier to put these values with a hash. can i do this directly replacing the above line, or do i need additional code that would relate something like.

my %args = ( h => $host, s => $source, d => $destination,);

i'd hate to create three variables that wouldnt be used through the sub other than to stick the values into a hash.

humbly -c

Replies are listed 'Best First'.
Re: passing subroutine arguments directly into a hash
by wog (Curate) on Oct 20, 2001 at 04:29 UTC
    One way of doing this assignment would be to use a hash slice on the left hand side of an assignment from @_:

    sub f { my %hash; @hash{ qw/ h s d / } = @_; # ... }

    (That hash slice is equivlent to ($hash{h}, $hash{s}, $hash{d}).)

Re: passing subroutine arguments directly into a hash
by demerphq (Chancellor) on Oct 20, 2001 at 16:30 UTC
    One thing is that you can pass a reference to a hash instead and then simply replace
    my ($host)=@_; $host="foo";
    with
    my $hash=shift; $hash->{host}="foo";
    Another issue I've found when passing user generated hashes, or hashrefs, as parameters is that its easy to make errors. Just misspell a key name and you've got an error, sometimes one that is hard to track down. So ive found its worth the time to add code to trap incorrect keys. This is nice because similar to drewbies and IraTarballs code it provides defaults as well. Which I find usefull in a constructor, one place where you tend to get named parameter calling conventions.
    package Foo; use strict; use Carp; use Data::Dumper; my %defaults=( a => "sn",b=>"afu" ); sub test{shift; print "test:",shift,"\n"} sub test2{&test}; sub new { my $class = shift; my $attrs = shift; my %params = @_; my $self=bless {%defaults},$class; for my $param (keys %params) { croak "Unknown param $param" unless exists($self->{$param}); $self->{$param}=$params{$param}; } for my $attr (keys %$attrs) { croak "Unknown attribute" unless $self->can($attr); $self->$attr($attrs->{$attr}); } return $self; } my $t=Foo->new( { test=>"Hello", test2=>"There", },a=>1,b=>2); my $tt=Foo->new(); print Dumper $t,$tt;
    It might be a bit long but the point was to show that with perl you have a lot of flexibility in how you can call a subroutine but that it leaves all the errorchecking to you.

    Yves
    --
    You are not ready to use symrefs unless you already know why they are bad. -- tadmc (CLPM)

      I have tried a number of approaches to this error checking problem. None of them completely satisfied me. But for the moment here is the one that seems to work best.

      What I do is having a function that does destructive manipulation of my arguments:

      # Takes a hashref, a key name, and an optional default. # Removes that key from the hash, and returns the value or # the default. Blows up if there is no value or default. sub excise_arg { my $args = shift; my $arg_name = shift; if (exists $args->{$arg_name}) { return delete $args->{$arg_name}; } elsif (@_) { return shift; } else { confess("Missing required argument '$arg_name'"); } }
      Call it without a default argument and you have a required key. Put in the default and it is optional. And since it is destructive, it gets rid of the keys and I can use this for a typo check.
      # Takes a hashref. Verifies that it is empty sub assert_args_done { my @left = keys %{ $_[0] }; if (@left) { confess("Unexpected arguments '@left' left over"); } }
      And now in a function I can do this:
      sub some_func { my $args = { @_ }; my $name = excise_arg($args, 'name'); # required my $age = excise_arg($args, 'age', undef); # optional assert_args_done($args); # Rest of the code here. }
      And if I want to take an existing function and wrap it in one that can handle some things itself and wraps the rest, it is easy. I just excise a few arguments and then pass the rest through untested. As long as they are tested somewhere, typos get checked.

      If anyone has alternate suggestions for how to handle this problem, I am open. This seems to work pretty well, but I have tried several things, and I don't claim that this is perfect.

      UPDATE
      Thanks Hofmator for catching my obvious typo. That is what I get for typing something up off of the top of my head. That is also why I use strict. :-)

        Nice. I agree with you in that its difficult to do this stuff elegantly, and this is a decent solution. One minor thought though is that you might want to use the poorly documented
        local $Carp::CarpLevel=1;
        before your confess calls to make them appear from the correct perspective. Other than that looks good and might get borrowed (if you dont mind...)

        Yves
        --
        You are not ready to use symrefs unless you already know why they are bad. -- tadmc (CLPM)

        sub excise_arg { my $args = shift; my $arg_name = shift; if (exists $args{$arg_name}) { return delete $args{$arg_name}; } [...]

        Shouldn't that be

        if (exists $args->{$arg_name}) { return delete $args->{$arg_name}; }
        as you are passing a hashreference into your function?

        Apart from that detail I really like this way :)

        -- Hofmator

Re: passing subroutine arguments directly into a hash
by kiseok7 (Beadle) on Oct 20, 2001 at 05:00 UTC
    how about this way? f(h=>1, s=>2, d=>3); sub f { my %hash = @_; }
Re: passing subroutine arguments directly into a hash
by IraTarball (Monk) on Oct 20, 2001 at 07:19 UTC
    If I understand you right you're looking for something like...
    @list = qw (one two three); %args = ( arg1 => 'string', arg2 => 5, arg3 => \@list ); sub1(%args); sub sub1 { my %args = ( arg1 => 'default', arg2 => 0, arg3 => undef, @_ ); print "$_ => $args{$_}\n" for keys %args; }
    For me this prints out
    arg1 => string arg2 => 5 arg3 => ARRAY(0x1a72f84)

    This allows you to pass in the arguments as a hash, provide defaults in the subroutine, and override them with arguments passed in. This is right out of 'Effective Perl Programming' by Joeseph Hall and Randal Schwartz.

    Ira,

    "So... What do all these little arrows mean?"
    ~unknown

      sub sub1 { my %args = ( arg1 => 'default', arg2 => 0, arg3 => undef, @_ ); }
      I personally prefer this approach because it 1) tells me what parameters I am expecting and 2) it allows me to set defaults. Yes, it takes up a lot of space visually, but who the heck cares since it makes the code that much more readable. :-)
      Hah! I finally understood something! Probably something I shd have understood a while ago... namely that a hash in array context is an array. So When I do
      @list = qw (one two three); %args = ( arg1 => 'string', arg2 => 5, arg3 => \@list ); sub1(%args); sub sub1 { print "$_\n" for @_; }
      I get
      arg1 string arg2 5 arg3 ARRAY(0x17656b4)
      Obvious when you think about it. I mention it here more to fix it in my own mind than in the faint hope of enlightening others, who are probably way ahead of me. But it does make sense for me of a lot of stuff I've been doing on trust, not 100% knowing why it works... like this way of passing arguments to HTML::Template.

      § George Sherston
        (well, technically its a list.... but thats for another day)

        Another "a-ha!" moment is just one more step away. The '=>' operator is really just a glorified comma. It will quote simple barewords on the LHS, but thats the only difference. Consider the following:

        my @a = (1 => 2 => 3 => 4); print "$_\n" for @a;
        So, the somewhat unfamiliar:
        (arg1=>$blah, arg2=>$blah2)
        is really just a very familiar four element list:
        ( 'arg1' , $blah , 'arg2' , $blah2 )
        dressed up with some syntactic sugar. (note the need to quote 'barewords' when using commas)

        With this in mind, take another look at your hash assignment and named argument passing.....

        -Blake

Re: passing subroutine arguments directly into a hash
by kjherron (Pilgrim) on Oct 20, 2001 at 08:18 UTC
    Well, there's always the obvious:
    my %args = ( h => $_[0], s => $_[1], d => $_[2] );