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
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}).)
| [reply] [d/l] [select] |
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) | [reply] [d/l] [select] |
|
# 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. :-) | [reply] [d/l] [select] |
|
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) | [reply] [d/l] |
|
|
|
|
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 | [reply] [d/l] [select] |
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 = @_;
}
| [reply] |
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 | [reply] [d/l] [select] |
|
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. :-) | [reply] [d/l] |
|
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 | [reply] [d/l] [select] |
|
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
| [reply] [d/l] [select] |
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] );
| [reply] [d/l] |
|
|