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

sub argument passing? (TIMTOWTDI)

by temporal (Pilgrim)
on Sep 13, 2012 at 18:36 UTC ( [id://993555]=perlquestion: print w/replies, xml ) Need Help??

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

I'd like to settle on a coding style for passing arguments to subroutines. I did some hunting on CPAN for a relevant module and didn't turn anything up (was surprised, actually). In the past I've done all of the following:

# single arg, used for simple and dirty sub foo { my $arg = shift; } # more flexible, I've been using this more frequently for complex subs sub bar { my ($arg1, $arg2, @optionals) = @_; } # I want more sophisticated argument handling for optional args, so I +pass a hash reference containing all the arguments in predefined keys sub bletch { my $href = shift; # will warn me if $href->{hello_sub} is not defined print 'hi!' if $href->{hello_sub}; }

I like sub bletch the best because it allows all of my arguments to be optional and I can pass a single reference without having to worry about arguments being in the correct order, the caller code is more legible because it specifies key names, etc.

I did, however, encounter a snag with this method. I have to check for existence of the key within the referenced hash before attempting to access it otherwise Perl warns me about accessing an uninitialized value if the argument was not set by the user (this warning doesn't seem to complain when accessing a hash directly, rather than through a deref). This led to me writing this code:

# processes arguments passed to subroutine in hashref # returns hash sub sub_opts { my $href = shift; error("bad options hashref: $href") if ref($href) ne 'HASH'; # get all options to return if they exist my %hash = map {$_ => exists $href->{$_} ? $href->{$_} : 0} keys % +{$href}; return %hash; } # implemented like: sub foobie { my %opts = sub_opts(shift); # won't warn me if $opts{hello_sub} doesn't exist, just treats it +as false print 'hi!' if $opts{hello_sub}; }

But now I'm creating a new hash from the ref which I'd rather not do. Not that I'm planning on creating any huge hashes but I consider it good practice not to needlessly replicate variables. But the alternative is checking if any argument I want to access exists before accessing it within each subroutine I write. What a (possibly dangerous) pain.

Anyway, I really feel like I am reinventing the wheel here and I'm sure some of my fellow monks out there have developed a sane scheme for subroutine argument handling. Or maybe there's a CPAN module that I missed.

Advice/critique/discussion? Thanks!

Replies are listed 'Best First'.
Re: sub argument passing? (TIMTOWTDI)
by toolic (Bishop) on Sep 13, 2012 at 18:52 UTC
      > Params::Check (Core module since 5.10)

      wow, thanks for listing this one...

      (...someone should start a thread about the most unknown core moduls...)

      Cheers Rolf

      Seconding the thanks for this reply. Not only some solid modules, I am now integrating Params::Check into large swaths of my code to increase their resilience to silliness and loving that it is a built-in.

      But the reason I'm replying again - I took a peek at Perl Best Practices and found it extremely useful for a Perl programmer who is still finding his coding style (me). Picked up a copy as a desk reference =)

      Strange things are afoot at the Circle-K.

Re: sub argument passing? (TIMTOWTDI)
by moritz (Cardinal) on Sep 13, 2012 at 18:50 UTC
    I did, however, encounter a snag with this method. I have to check for existence of the key within the referenced hash before attempting to access it otherwise Perl warns me about accessing an uninitialized value if the argument was not set by the user

    If the caller doesn't pass the values you expect, you'll get a warning in the traditional style too:

    $ perl -wE 'sub f { my ($v) = @_; say $v }; f()' Use of uninitialized value $v in say at -e line 1. $

    In the end the level of checking you want to do is a matter of style, and intended audience.

    In Perl 5 code I usually only check the arguments where I suspect the subs might get called wrongly. In Perl 6 I just write the signatures as expressive as possible, and let the compiler and runtime do the checking for me.

Re: sub argument passing? (TIMTOWTDI)
by LanX (Saint) on Sep 13, 2012 at 20:21 UTC
Re: sub argument passing? (TIMTOWTDI)
by temporal (Pilgrim) on Sep 13, 2012 at 21:47 UTC

    Thanks guys, great suggestions, +1

    Spent some time looking at Params::Check - seems perfect for what I want.

    Aside from the fact that I'll have to define templates for all my subs, but I'm sure that I can come up with some way to dynamically create templates. Even still, it's easier than handwriting the logic myself.

    On a related note, I was trying to sort using different comparison code blocks which are passed as args (within the hashref model above) to subs:

    #! perl -w use strict; use Params::Check qw(check last_error); my $verbose = 1; my_func( { test_block => sub {$b cmp $a} } ); sub my_func { my $sort_function; my $tmpl = { test_block => { required => 1, defined => 1, default => sub {$a cmp $b}, strict_type => 1, store => \$sort_function} }; check($tmpl, shift, $verbose) or warn ('problem with check: ' . Params::Check::last_error +()); my @stuff = qw(foo bie bletch a z); @stuff = sort $sort_function @stuff; print join "\n", @stuff; }

    Took a bit of tinkering to figure out how all that needed to be written to work properly so I thought it might be helpful to somebody if I posted it up here as well.

    Thanks again, brothers.

    Strange things are afoot at the Circle-K.

Re: sub argument passing? (TIMTOWTDI)
by tobyink (Canon) on Sep 13, 2012 at 20:47 UTC
    no warnings qw(uninitialized);
    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
Re: sub argument passing? (TIMTOWTDI)
by Tanktalus (Canon) on Sep 20, 2012 at 03:07 UTC

    Personally, I go with passing in hash refs most of the time, or shifting off when I only have a single parameter. And in all cases, I just deal with undefs as appropriate. Which sometimes means don't do anything about them - I want the fatal warning (I enable fatal warnings, too, did I forget to mention that?). If it's optional, I use the defined-or operator, // to use either the value or a default:

    sub say_hello { my $opts = shift; my $hello_sub = $opts->{hello_sub} // sub { my $o = shift; say "hell +o, ", $o->{who} // 'world' }; $hello_sub->($opts); } say_hello(); # says "hello, world" say_hello({ who => 'temporal'}); # says "hello, temporal" say_hello({ hello_sub => sub { my $o = shift; say "wazzup, ", $o->{zup +} // 'homie' }, zup => 'bro' }); # says "wazzup, bro"
    Ok, so the example is getting a bit ridiculous, but you get the idea :-)

    I don't check parameters. Almost ever. I've been bitten by too many tools trying to be too smart about their inputs, but when I feed them objects with string overloading or filehandles that aren't files and they fail just because the author didn't realise how automorphic perl variables can be, it gets annoying. Don't assume that the person calling your code is less smart than you. Carp if you have to, but it might just work if you try.

Re: sub argument passing? (TIMTOWTDI)
by protist (Monk) on Sep 16, 2012 at 15:54 UTC

    There is another approach to this problem: prototyping.

    #!/usr/bin/perl use warnings; use strict; #prototyped to explicitly state acceptable arguments sub addstuff($$$); my $D=4; my $E=3; my $F=2; #gives error if too few or too many arguments #(or arguements of wrong type) my $result = addstuff $D, $E, $F; print $result."\n"; #prototyped to match sub addstuff($$$){ my $A=shift; my $B=shift; my $C=shift; return $A+$B+$C; }

        Thanks AnomalousMonk, I am following your advice and already seeing some issues that I'll run into with using Perl's prototyping such as it is.

        'Enjoyable light reading' indeed, protist! Maybe just enjoyable ;)

        Strange things are afoot at the Circle-K.

      Cool, didn't know that Perl supported function prototyping. Looks a bit quicker and dirtier than using Params::Check - fewer filtering options, no default values, etc.

      I am gravitating towards creating a separate file which would contain all of my function prototypes and then I would require that in my module. My module has quite a few functions so this might be the easiest way to keep track of them all and have a single point of change rather than having to track down all that template-building legwork within the subroutines themselves that Params::Check requires.

      Not quite as legible, though. And to heed the docs warning, I could get into trouble writing prototypes for already written functions. So I'll have to do a little checking to make sure I'm not doing anything too silly with how arguments are being passed and unpacked.

      Anyway, thanks for pointing this out protist =)

      Strange things are afoot at the Circle-K.

        Cool, didn't know that Perl supported function prototyping.

        It doesn't. It's more like 'function argument context prototyping'. Please read the highly recommended Far More Than...

        You are welcome. :)

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (7)
As of 2024-04-25 15:33 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found