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


in reply to Re: passing subroutine arguments directly into a hash
in thread passing subroutine arguments directly into a hash

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. :-)

Replies are listed 'Best First'.
Re: Re (tilly) 2: passing subroutine arguments directly into a hash
by demerphq (Chancellor) on Oct 21, 2001 at 04:47 UTC
    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.)-:
        Whooo. Well that told me. :-) Heh.

        Good point about incrementing the carplevel instead of setting it, that would be a more clever way to go about it.

        But im a little confused about your other comments. Maybe a post to meditations about what you've found out? I for one would appreciate it, especially as I have used carplevel a number of times with good results (or else I wouldnt have brought it up eh?). I understand from gschwern that there is a new version (written by you perhaps?) of Carp expected for 5.7 (or something) that corrects at least one bug in it that I know of (that being the fact that when overloading stringify in objects it will use this stringification instead of printing the underlying string value of the reference, which can cause big problems (sometimes deep recursion).

        Nonetheless these are the type of things that I would really like to see a meditation on, so if you've got a few minutes... :-)

        Its funny though but I was just working on some code that does something similar in Benchmark. Benchmark wants to know its original callers package so that it can run its benchmarks inside of that lexical space. As I have rewritten Benchmark in an OO form the routine is no longer at the same depth in the call stack, nor is it called in the same way, and it will be being from various subclasses with their own call stack issues, accordingly I had to hack the routine to ignore any packages in the Benchmark heirarchy. Wasn't my preferred solution and makes me think that maybe this type of 'figure out the outside caller' routine needs to be carefully coded in a reusable form. In fact on that line, I would be willing to put some effort into that if you have some code or ideas to contribute. ...oO(Hmm, maybe a meditation in of itself)

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

Re: Re (tilly) 2: passing subroutine arguments directly into a hash
by Hofmator (Curate) on Oct 22, 2001 at 14:31 UTC
    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