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

I've written some function with multiple options. Suddenly I'm up to > 13 arguments, with most of the places I invoke it passing nulls in most of the args. Of course, this is error prone and difficult to maintain.

My background is writing LISP code. I'm used to its keyword style of function-definition. In spirit, it is very similar to Getopt. My inclination is to rewrite the function definition to use Getopt. But it seems I never see Getopt used down in function definitions; I only see it processing the arguments to scripts. Are there good reasons to shy away from code of the form

use Getopt::Std; sub foo{ my($opt_a, $opt_b, $opt_c, $opt_d); getopts('abc:d:'); ...}
Efficiency won't be a concern here; my main concerns are clarity of the code, maintainability and portability.

Also, if Getopt::Std gets the job done, is there any particular reason1 to use any of the other half-dozen or so 'Getopt' modules at CPAN instead?

throop


1Other than the fact that most people's first thought whent they see STD is 'Sexually Transmitted Disease' or 'Severe Tire Damage'.

Replies are listed 'Best First'.
Re: Using a 'getopt' in module code
by brian_d_foy (Abbot) on Dec 04, 2006 at 16:16 UTC

    If you're up to 13 options, and most of them are not used together, its time to be nice to the users by providing a higher level interface to function that takes care of most of those details. You can curry the function.

    Each wrapper function represents a particular way of calling the general function so the programmer doesn't have to know about all of the other options. They just know what they want to acccomplish and the wrapper function takes care of the other details. If you change the general function, the programmer doesn't really care as long as the wrapper functions still do their work correctly.

    sub general_function_with_lots_of_args { ... } sub do_it_with_name { general_function_with_lots_of_args( undef, undef, $_[0], undef, .. +. ) } sub do_it_using_id { general_function_with_lots_of_args( undef, $_[0], undef, ... ) } sub do_it_this_way { ... } sub do_it_that_way { ... }

    As for the various Getopt modules, there are indeed quite a few. However, there are quite a few ways that programs take command line arguments too. I talk about some of these in my configuration chapter in Mastering Perl.

    --
    brian d foy <brian@stonehenge.com>
    Subscribe to The Perl Review
      Thanks for the several good replies. Clearly from reading them, my better approach is to pass the function arguments as a hash. I think you've saved me from writing some truly crufty code.

      And thanks for the ideas about the various Getopt modules.

      throop

Re: Using a 'getopt' in module code
by Fletch (Bishop) on Dec 04, 2006 at 15:58 UTC

    Aside from the fact that getopts is going to try and read from @ARGV and not @_, the usual means of getting keyword-y arguments for subroutines is to use @_ to initialize a hash.

    sub blortz { my @defaults = qw( foo 1 baz 42 ); my %args = ( @defaults, @_ ); print "baz is the answer\n" if $args{baz} == 42; } blortz( baz => 2.71828 );
Re: Using a 'getopt' in module code
by davorg (Chancellor) on Dec 04, 2006 at 15:59 UTC

    Getopt works on @ARGV, so you can't use it to process function arguments. Well, not without doing @ARGV = @_ which might well break other stuff.

    In situations like this, most people would probably pass the function arguments as a hash.

    my_func(foo => 1, bar => 2); sub my_func { my %args = @_; if ($args{foo}) { ... } # etc... }
    --
    <http://dave.org.uk>

    "The first rule of Perl club is you do not talk about Perl club."
    -- Chip Salzenberg

      Getopt works on @ARGV, so you can't use it to process function arguments. Well, not without doing @ARGV = @_ which might well break other stuff.

      You can try something like this:

      #!/usr/bin/perl use strict; use warnings; use Getopt::Std; use Data::Dumper; sub getopt_fun { my %args; { local @ARGV = @_; getopt 't', \%args; } print Dumper \%args; } getopt_fun (-t => 'foo');

      Edit: narrowed the scope of the local.

Re: Using a 'getopt' in module code
by ikegami (Patriarch) on Dec 04, 2006 at 17:17 UTC

    Yet another method is to use a callback. For example,

    mysort(\@list, 'age', 'asc', 'name', 'asc');

    can be replaced with

    mysort(sub { $_[0]{age} <=> $_[1]{age} || $_[0]{name} cmp $_[1]{name} }, @list);

    With the right prototype (mysort (&@) { ... }), the above can also be written as

    mysort { $_[0]{age} <=> $_[1]{age} || $_[0]{name} cmp $_[1]{name} } @list;

    I haven't read it, but I believe the book "Higher Order Perl" has a lot on this subject.

Re: Using a 'getopt' in module code
by Firefly258 (Beadle) on Dec 04, 2006 at 16:22 UTC
    Why don't you rewrite the subroutine to take named parameters as a hash like so?

    sub func { my %args = ( # default options option1 => "Foo", option2 => "Bar", @_, # passed options ); if ($args{option1}) { ... } if ($args{option2}) { ... } } func(option1 => "Foozball", option2 => "Barracuda");


    perl -e '$,=$",$_=(split/\W/,$^X)[y[eval]]]+--$_],print+just,another,split,hack'er
Re: Using a 'getopt' in module code
by djp (Hermit) on Dec 05, 2006 at 03:15 UTC
Re: Using a 'getopt' in module code
by Anonymous Monk on Dec 04, 2006 at 23:23 UTC

    Just to be safe, I'm going to post this as AM.

    I work with a set of Perl libraries that were designed to use Getopt::Long as the standard parameter-passing mechanism. (Oddly, only with single letter variants.)

    Among the many things that sucks about this are:

    • You cannot pass nested structures, callbacks, references. Objects. Filehandles. Anything but a string. You might not want that now, but trust me, having a OO framework where methods can't take references is suck.
    • All strings are copied many times... normally into an "-i$num" string before passing, then on to the stack, then into @ARGV, then into getopt, then into a destination variable. When passing large buffers into methods, this is suck.
    • All numeric arguments are cast to opt-prefixed strings, then back to numbers. This is especially suck if you happen to like retaining floating point accuracy.

    At one point I had a longer list. It just made me sad, though. Don't do it. Your code maintainers will loath you.

Re: Using a 'getopt' in module code
by slloyd (Hermit) on Dec 05, 2006 at 00:11 UTC
    I suggest you pass in a hash instead of a simple array so that your arguements do not have to be numbered.
    if(myfunction(debug=>1,var=>$val)){...} sub myfunction{ my @params=@_; if($params{debug}){...} }

    -------------------------------
    Need a good Perl friendly Host Provider?
    http://www.dreamhost.com

Re: Using a 'getopt' in module code
by revence27 (Novice) on Dec 05, 2006 at 08:42 UTC
    I ended up with similar problems often enough, so I decided to write something targetted for that particular problem. Most of the solutions I found had a heavy Unix slant, and yet I don't remember a single time I wrote Perl that wasn't meant for Win32, although I never write it on Win32.

    So, here it is, in the Code Catacombs.

    It has a strong Lispic feel, since it lets you write the predicates that choose the arguments that will be classed. And ... hey, just read the POD.

    I should change it to allow more than one argument as a value of the key arguments (something like -o file1 file2 (think GCC args), in which case '-o' will be associated with both 'file1' and 'file2', and enable GCC, to write multiple similar executables with those kinds of args). That's very easy to change, and you can add that in one sweep. My code, by the way, is very clear. I pride myself in that — nobody can take that away from me! :o). It's object-oriented, of course.

    Tell I what you think.

    (PS: I have it in Py and Clisp, as well, where it has the above-mentioned feature ;o)



    print "Something's gone wrong with my head: $!";
Re: Using a 'getopt' in module code
by nacredata (Sexton) on Dec 06, 2006 at 05:50 UTC
    How about using the trick of passing in a reference to a hash to hold the arguments? http://www.oreilly.com/catalog/perlbp/chapter/ch09.pdf