Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

Re: passing subroutine arguments directly into a hash

by demerphq (Chancellor)
on Oct 20, 2001 at 16:30 UTC ( #120238=note: print w/replies, xml ) Need Help??


in reply to passing subroutine arguments directly into a hash

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)

Replies are listed 'Best First'.
Re (tilly) 2: passing subroutine arguments directly into a hash
by tilly (Archbishop) on Oct 20, 2001 at 19:31 UTC
    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)

        Never.

        Manipulating $Carp::CarpLevel would be a bad idea normally simply because $Carp::CarpLevel is an internal interface that is not intended to be used outside of the core. But it gets worse.

        $Carp::CarpLevel is simply a horrible hack. I refuse to use it. In fact getting rid of mistakes in Exporter that were caused by misunderstandings of how $Carp::CarpLevel works was important enough for me to decide to rewrite Carp. And I did the rewrite with a much saner alternative, and I was going to proceed and kill $Carp::CarpLevel entirely.

        That was a project that I decided to give up on once I realized that the warnings pragma had likewise gotten it totally wrong internally, and I was not up to reversing it and correcting the misunderstandings. (Incidentally virtually everywhere where it was used, I found that it was misunderstood.) So for the indefinite future $Carp::CarpLevel is likely to remain an undocumented internal interface which is deprecated. And if someone else gets irritated with the fact that warnings will sometimes skip a ton of levels that it shouldn't skip, and turn a croak into a confess coming from the guts of the internals, it could easily go away.

        Therefore I strongly think that it should not be used. Ever.

        Incidentally a case in point for why the internal hackery is such a bad design. In the above, your game with the carp level just broke anyone else trying to play with it. The right way if you wanted to do it is to do:

        local $Carp::CarpLevel = $Carp::CarpLevel + 1;
        Of course this only works if you are going to confess or cluck at the end. If you are going to carp or croak, you will be so outta luck because it doesn't work anything like you would predict.

        Incidentally in Perl 5.8 the right way to achieve the effect that you want will be to export the functions from a module, and in that module do:

        $Carp::Ignore{ __PACKAGE__ } = 1;
        Which tells the rewritten Carp that the current package is one which it should not start a stack backtrace from. :-(I don't remember if I left this documented or not. I really should wrap that project up a bit better.)-:
      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

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://120238]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others examining the Monastery: (5)
As of 2021-05-16 21:25 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Perl 7 will be out ...





    Results (152 votes). Check out past polls.

    Notices?