Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Your named arguments

by Juerd (Abbot)
on Nov 07, 2005 at 21:07 UTC ( #506528=perlmeditation: print w/replies, xml ) Need Help??

See this as a poll, but also as a request for discussion. If I've missed a previous thread, please let me know.

How do you do named arguments in Perl 5?

Do you use a hash? A module to check things? Maybe a source filter? Do warn or die or do nothing at all, in case of unknown keys?

Please provide an example of how you would handle this contrived and simple case, as a teaser presented in Perl 6:

sub convert (:$from!, :$to!, :$thing!) { ... }
The sub &convert requires exactly three named arguments (as indicated by the colons): from, to, and thing. They have to be specified (as indicated by the exclamation points), but may be undef. Of course, if you don't check for these things usually, don't include checks in your example. Here, the arguments are accessible through $from, $to and $thing in the sub itself, but if you access things through a hash, leave it that way.

If you use a hash, what is it called? arg? argv? args? param? params? %_? Is it copied from @_, or do you require the caller to pass a reference? Do you allow both?

It'll be interesting to see the colorful spectrum of what we all use.

Juerd # { site => 'juerd.nl', plp_site => 'plp.juerd.nl', do_not_use => 'spamtrap' }


Update: Added exclamation points. :$foo defaults to being optional.

Replies are listed 'Best First'.
Re: Your named arguments
by samtregar (Abbot) on Nov 07, 2005 at 21:33 UTC
    I use Params::Validate:

    use Params::Validate qw(validate); sub convert { my %args = validate(@_, { from => 1, to => 1, thing => 1}); # ... }

    Constructing a more interesting example would require knowing what kind of data should be in the parameters. Params::Validate has some decent built-in type-checks and makes rolling your own easy.

    -sam

Re: Your named arguments
by vek (Prior) on Nov 07, 2005 at 23:09 UTC
Re: Your named arguments
by sauoq (Abbot) on Nov 07, 2005 at 22:47 UTC

    I usually just use hashrefs. The only validation I do is ensuring that the keys I need exist (and any validation I may have to do on individual arguments.) Extraneous arguments are, in particular, fine by me. This comes in handy when I have more than one function that take different but overlapping sets of parameters.

    -sauoq
    "My two cents aren't worth a dime.";
    
Re: Your named arguments
by TedYoung (Deacon) on Nov 07, 2005 at 21:37 UTC

    Personally, I never really cared about fancy prototype handling. I just:

    my ($this, $a, $b) = @_;

    in my code. When doing named params, I will normally do something like %h and then immediately convert then into specific vars.

    My reason for posting was I saw your example:

    sub convert (:$from, :$to, :$thing) { ... }

    It occurred to me that it would be rather easy to support such a thing WIHTOUT sourcefilters thanks to the prototype function.

    There are dozens of potential impls. using prototype and I will leave that as an excersize to the readers. But, I don't think I have ever seen a module that does that without source filters. Then again, I haven't looked very hard.

    Update: Working on an example of said package.

    Update: Here is a quicky that I through together. I have to go home, so I can't spend too much time on this, but I can see why no one has done this before. The code I wrote is:

    Ted Young

    ($$<<$$=>$$<=>$$<=$$>>$$) always returns 1. :-)

      It's not hard to modify it so that the location of the use statement doesn't matter:

      package Signatures; my @packages; sub import { my $package = caller; push @packages, $package; } INIT { for my $package (@packages) { no warnings; no strict; for (keys %{ $package . '::' }) { next unless exists &{ $package . "::$_" }; my $sub = \&{ $package . "::$_" }; next unless my $proto = prototype $sub; eval qq { package $package; sub $_ { local ($proto) = \@_; \$sub->() } } } } } 1;
      It still seems like the function using the prototype has to be defined AFTER any calls to it though. And modules using the Signatures have to be used after all calls to the functions in them. Otherwise I get "Malformed prototype for foo: $a,$b at C:\temp\Proto\test.pl line 6." Not sure if there is any way around that. And I don't see any way to lexicalize the parameters and make strict happy.

      Jenda
      XML sucks. Badly. SOAP on the other hand is the most powerfull vacuum pump ever invented.

Re: Your named arguments
by duelafn (Vicar) on Nov 08, 2005 at 00:39 UTC
      That solution morphed into Params::Named and would be applied like so:
      use Params::Named; sub convert { MAPARGS \my($from, $to, $thing); ... }
      HTH

      _________
      broquaint

Re: Your named arguments
by creamygoodness (Curate) on Nov 08, 2005 at 04:45 UTC

    Not satisfied with any of the Class:: modules as the basis for the 50+ module distro I'm working on, I rolled my own. It's too specific to be published independently, but here's the rundown...

    The following routine, which simply verifies that label names exist in a defaults hash, is always available, though it is called almost exclusively by a single constructor which nearly all the classes inherit.

    For the small minority of performance-critical subs, such as constructors for frequently instantiated classes, I try to design things so that it's reasonably safe to skip verification -- for example requiring by very few arguments, or by using clone() against a template.

    All subclasses of the base class KinoSearch::Util::Class inherit new() and init_instance() (among others).

    • Each class maintains a template hash of instance variables in the package global %MyPackage::instance_vars.
    • new() uses clone() (from the CPAN distro Clone) to produce a deep copy of %MyPackage::instance_vars, label names in @_ are verified, and @_ is merged into the cloned copy. Before returning, new() calls init_instance() against the blessed ref.
    • init_instance() is a no-op by default. If more robust parameter checking or complex initialization is required, it is done via overriding init_instance().

    Here's the important stuff from the base class:

    Here's the Analyzer abstract class which defines a single instance variable, language, and a single sub, analyze.

    ... and here's the PolyAnalyzer class which inherits from it. new(), which is inherited from KinoSearch::Util::Class, verifies the label names. init_instance() does a little bit more, checking to make sure that either language or analyzers is defined.

    The parameter verification in PolyAnalyzer's init_instance isn't that strong -- language isn't tested to see if it's a valid value, and analyzers isn't checked to verify that it's an arrayref -- but that's because I know that meaningful exceptions will be thrown somewhere down the line if invalid values are supplied.

    The OO design is heavily influenced by Java, as the project is a loose port of Java Lucene and this scheme allows for a lot of parallel coding. However, the signature-based parameter verification in Lucene often leaves me scratching my head, e.g. as to what the "true" in fooThis(query, file, true) does. With signatures unavailable, I'm forced to use named parameters in Perl -- but in some ways I like 'em better anyway.

    --
    Marvin Humphrey
    Rectangular Research ― http://www.rectangular.com
Re: Your named arguments
by Perl Mouse (Chaplain) on Nov 08, 2005 at 10:39 UTC
    I'd do:
    sub convert { my %arg = @_; my ($from, $to, $thing) = @arg{qw /from to thing/}; ... }
    I don't enforce policies of "even if you want to pass in an argument with an undefined value, you must specify it" on my callers. I'm a Perl programmer, so my programs are programmer friendly. Absence of named parameters are fine. I don't balk at extra arguments either - this can be very useful, as wrappers can just call the function with whatever @_ they were called with.

    On the other hand, I might work with defaults. Then I'd write it as:

    sub convert { my %arg = @_; my $from = exist $arg{from} ? $arg{from} : 'default'; my $to = exist $arg{to} ? $arg{to} : 'default'; my $thing = exist $arg{thing} ? $arg{thing} : 'default'; ... }
    Or:
    sub convert { my %arg = (from => 'default', to => 'default', thing => 'default', @_); my ($from, $to, $thing) = @arg{qw /from to thing/}; ... }
    Or when passing in a false value doesn't make sense, I'd make it that any false value means 'default':
    sub convert { my %arg = @_; my $from = $arg{from} || 'default'; my $to = $arg{to} || 'default'; my $thing = $arg{thing} || 'default'; ... }
    And when I want to do something fancy, I use Getopt::Long:
    sub convert { local @ARGV = @_; GetOptions ('from:s' => \my $from, 'to:s' => \my $to, 'thing:s' => \my $thing); ... }
    Perl --((8:>*
      There's more than one way to be friendly. :)
      I don't balk at extra arguments either - this can be very useful, as wrappers can just call the function with whatever @_ they were called with.

      The downside of allowing extra args is of course the classic misspelled_lable => 'uh-oh' problem. If such an error isn't caught right away, that can result in some very unfriendly bugs, depending. Do you perform any extra verification to defend against that class of problem?

      Unchecked labels really get ugly if there are default values...

      --
      Marvin Humphrey
      Rectangular Research ― http://www.rectangular.com
        The downside of allowing extra args is of course the classic misspelled_lable => 'uh-oh' problem. If such an error isn't caught right away, that can result in some very unfriendly bugs, depending. Do you perform any extra verification to defend against that class of problem?

        One of the many reasons I love my test suite :-)

Re: Your named arguments
by etcshadow (Priest) on Nov 08, 2005 at 04:57 UTC
    By the standard we use where I work, it would look like this:
    sub convert { my ($args) = @_; AssertRequiredFields($args, [qw( from to thing )]); AssertValidFields( $args, [qw( from to thing )]); ... }
    where AssertRequiredFields and AssertValidFields are fairly obvious core utility functions (which confess if the assertion is invalid), and $args is a hashref.

    Yes, I know that it looks a little outdated... However, even though the codebase is under active development, some of it is still very old. Since nobody really feels like going back and retrofitting a million lines of code, we continue with the old (but still pretty good) standard.

    ------------ :Wq Not an editor command: Wq
Re: Your named arguments
by dragonchild (Archbishop) on Nov 08, 2005 at 02:34 UTC
    sub convert { my ($o) = @_; return if grep { !exists $o->{$_} } qw( from to thing ); ... }
    I fail to see the confusion. So what if I use $o->{from} instead of $from? That's just sugar that has a slight readability edge, but not enough to make my P5 code more convoluted. It's a micro-optimization. The bigger gains are naming at all, <50 line subs, 78char lines, and the like.

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

      Confusion? I can understand why you fail to see it, no one mentioned any confusion. ;)

      BTW That was just a simple example of a wired range function signatures avaiable in p6. See http://dev.perl.org/perl6/doc/design/syn/S06.html for all the new options in signatures includeing optional, required, positional, named, slurpy array, and slurpy hash params.


      ___________
      Eric Hodges $_='y==QAe=e?y==QG@>@?iy==QVq?f?=a@iG?=QQ=Q?9'; s/(.)/ord($1)-50/eigs;tr/6123457/- \/|\\\_\n/;print;
        I've been following the thread with not a little bemusement. I think that some people on the list are conflating optional with "will be everywhere!" Heck, I still look up the arguments for splice(). I fully expect to be looking up the various signature types for at least a year or two, and that's ok.

        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
      Don't think of it as confusion.

      Think of it as an opportunity to do something like strict.pm for argument parameters. Furthermore it is particularly important here because the two places that need to synchronize (subroutine definition and call) are generally far apart in the text, so it is easy to miss a mismatch.

        I actually had this very discussion with stvn this morning, but with respect to Ruby's signatures. To learn Ruby, I've been porting a Tree module that I just wrote in Perl to Ruby. One of the things I would like to be able to do is:
        def add_child( Hash options is optional, Tree *children ) # Subroutine body here end
        Since I cannot do that, I have to do something like:
        def add_child( *children ) options = children[0].is_a( 'Hash' ) ? children.shift : Hash.new; if children.any? { |node| !node.is_a( 'Tree' ) }: raise ArgumentError "Non-Tree argument found in add_child()" end # Resume regular processing here end
        I'd much rather have the subroutine signature do that for me. Perl6 will have this and I hope Ruby2.0 will, but we'll have to see.

        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: Your named arguments
by adrianh (Chancellor) on Nov 08, 2005 at 15:17 UTC
    Please provide an example of how you would handle this contrived and simple case, as a teaser presented in Perl 6:

    Most of the time I would do something like:

    sub convert { my ( $from, $to, $thing ) = @{ {@_} }{ qw( from to thing ) }; ... }

    With in-house code I find that my test suite reveals where I accidentally miss required parameters, so I only add explicit code inside the subroutine to check if it's an API I'm presenting to others.

Re: Your named arguments
by bageler (Hermit) on Nov 08, 2005 at 18:32 UTC
    Here's the framework I use for new() subroutines for objects:
    sub new { my ($class,%args) = @_; my $self = { %args }; bless $class, $self; $self->init(); }
      Hi, I think you have a typo and an inefficiency in there. The bless line should be...

          bless $self, $class;

      ... or if you want objects to be able to spawn like objects...

          bless $self, ref($class) || $class;

      The ineffieciency comes from rolling the %args hash twice. It would be a little swifter if new looked like this instead...

      sub new { my $class = shift; my $self = { @_ }; bless $self, $class; $self->init(); }

      Lastly, the code counts on the init() sub returning $self, otherwise you'll get an error. That's an interesting choice. My version of init() doesn't, but it's only come up when trying to build a constructor that might return different object types based on the parameters. In that case, I had to override new(). I can see the merits of the alternative, though.

      --
      Marvin Humphrey
      Rectangular Research ― http://www.rectangular.com
        yes, that's what I meant :) That's what I get for posting after drinking.
Re: Your named arguments
by Aragorn (Curate) on Nov 09, 2005 at 08:01 UTC
    At work we use Params::Check. Reasonable simple, and it does the job. The example in the documentation uses %hash, which I don't approve of. I use %args_in which covers the meaning nicely. Maybe %params_in would be even better.

    Arjen

    All that is gold does not glitter...

      I use %args_in which covers the meaning nicely. Maybe %params_in would be even better.

      No, parameters are expected, while arguments are passed. The hash contains arguments, never parameters.

      Given foo => $bar, the parameter is "foo", and the argument is $bar, named by the name of the parameter.

      In Perl 6:

      sub foo ($foo, $bar) { # $foo and $bar are *parameters* # But in the sub itself, they represent the *arguments* ... } foo(42, 15); # positional arguments foo(foo => 42, bar => 15); # named arguments foo(bar => 15, foo => 42); # same thing :)

      Juerd # { site => 'juerd.nl', plp_site => 'plp.juerd.nl', do_not_use => 'spamtrap' }

Re: Your named arguments
by snowhare (Friar) on Nov 10, 2005 at 04:02 UTC
    My normal way for three required parameters is:
    use Class::ParmList qw(simple_parms); sub convert { my ($from, $to, $thing) = simple_parms([qw(from to thing)], @_); #... }
    When I finish my Sub::Parms module that will become (using the default 'required' mode):
    use Sub::Parms; sub convert { BindParms : ( my $from := from; my $to := to; my $thing := thing; ) #... }

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others cooling their heels in the Monastery: (3)
As of 2020-07-07 05:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?