Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

Omitted subroutine arguments

by tel2 (Pilgrim)
on Mar 15, 2011 at 08:59 UTC ( [id://893261]=perlquestion: print w/replies, xml ) Need Help??

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

Hi Monks,

I'm writing a subroutine which takes a variable number of arguments.  Sometimes the 2nd argument is absent, and for simplicity/conciseness I was hoping to be able to just omit it from the sub call, but it looks as if I might need to explicitly specify '' for that argument.  Is that correct?  Check this code out:

test1('A1',,'A3','A4'); test1('B1','','B3','B4'); sub test1 { print "args=" . join(',',@_) . "\n"; my ($arg1, $arg2) = (shift @_, shift @_); my @argrest = @_; print "arg1=$arg1, arg2=$arg2, argrest=@argrest\n\n"; }
The output is:
args=A1,A3,A4 arg1=A1, arg2=A3, argrest=A4 args=B1,,B3,B4 arg1=B1, arg2=, argrest=B3 B4

As you can see, it looks as if the 2nd argument is completely ignored, unless I explicitly supply it.

Questions:

1. Why does the 3rd argument seem to be seen as the 2nd when the 2nd is omitted? (Please don't say "because the 2nd is omitted").

2. Will I really need to supply the 2nd argument as '' or is there a simple way to get omitted arguments to be recognised?

Thanks.

Replies are listed 'Best First'.
Re: Omitted subroutine arguments
by moritz (Cardinal) on Mar 15, 2011 at 09:17 UTC

    arg1, , arg3 and arg1, arg3 are exactly the same to perl, it simply ignores the additional comma.

    So you can't really omit an argument. In Perl 5, arguments are simply passed as a list. You can use an empty string or undef or any value that pleases you, but "omitting" doesn't really work.

    Maybe it will make your code clearer if pass a hash reference of arguments instead?

    draw({ what => 'circle', cx => 100, cy => 50, radius => 2, })

    or some such.

Re: Omitted subroutine arguments
by Eliya (Vicar) on Mar 15, 2011 at 09:15 UTC

    The arguments to a function are being interpreted as a list, and successive commas do not act as a placeholder for empty elements (yes, I've also at times wished this would work...).

    From perldata:

    The null list is represented by (). Interpolating it in a list has no effect. Thus ((),(),()) is equivalent to (). Similarly, interpolating an array with no elements is the same as if no array had been interpolated at that point.

    This interpolation combines with the facts that the opening and closing parentheses are optional (except when necessary for precedence) and lists may end with an optional comma to mean that multiple commas within lists are legal syntax. The list "1,,3" is a concatenation of two lists, "1," and 3, the first of which ends with that optional comma. "1,,3" is "(1,),(3)" is "1,3" (And similarly for "1,,,3" is "(1,),(,),3" is "1,3" and so on.)

      > (yes, I've also at times wished this would work...).

      it has maintenance advantages in long lists if trailing commas don't count

      %hash= ( key1 => value1, key2 => value2, key3 => value3, key4 => value4, key5 => value5, )

      IIRC I heard users of other languages complaining about this "missing feature".

      Well you can't have it all ...

      Cheers Rolf

Re: Omitted subroutine arguments
by cdarke (Prior) on Mar 15, 2011 at 09:36 UTC
    An alternative is to use 'named parameters' instead. This is just a hack where we collect a hash from the parameter line (%args) and merge it into the defaults (%def below):
    use warnings; use strict; test1(arg1=>'A1',arg3=>'A3',arg4=>'A4'); test1(arg1=>'B1',arg2=>'',arg3=>'B3',arg4=>'B4'); sub test1 { my %def = (arg1 => 'A1', arg2 => 'A2', arg3 => 'A3', arg4 => 'A4'); my %args = (%def, @_); (keys %def != keys %args) and die "Invalid argument"; local $, = ' '; print %args,"\n"; }
    The output is:
    arg2 A2 arg1 A1 arg4 A4 arg3 A3 arg2 arg1 B1 arg4 B4 arg3 B3
    Passed argameters can be obtained from the hash using the parameter name as the key, for example $args{arg1}
Re: Omitted subroutine arguments
by chrestomanci (Priest) on Mar 15, 2011 at 09:21 UTC

    Hello tel2.

    In perl function calls, the caller passes an array to the function, which the function sees as the @_ array. The convention is to treat the members of the array as positional arguments.

    The reason that omitted arguments are skipped is because perl collapses multiple commas into one when building the array of arguments to pass to the function.

    There are three ways you handle optional arguments.

    1. You can order the arguments so the optional ones appear after any required ones.
    2. You can pass in '' or undef for any omitted arguments
    3. You can pass the arguments as a hash, or get the fuction to treat it's arguments as a hash.

    You will see the last method (arguments as a hash) is quite common on a lot of CPAN modules. It is very powerful, but it does require the function caller to name all the arguments. This is often seen as a good thing as it makes code self documenting.

Re: Omitted subroutine arguments
by Ratazong (Monsignor) on Mar 15, 2011 at 09:14 UTC

    Try the following:

    my @x = ("a1","a2","a3",,,,,,"an",); print @x, "size: ", scalar(@x);
    As you can see, @x does not contain any "empty values", "only" a1, a2, a3 and an. Or -if you look at it the other way around- you assign some values to @x, each seperated by one (or more!) comma.

    So in your first case, you only provide 3 arguments... and nothing is "omitted".

    HTH, Rata

    note: the fact that no elements are created where no values are provided is often very helpful!
Re: Omitted subroutine arguments
by thargas (Deacon) on Mar 15, 2011 at 11:59 UTC

    The best I can think of (apart from the already mentioned named parameters) would be to use undef for the arg you don't want to supply. Not as simple as ",,", but more obvious, which could be counted as a plus.

Re: Omitted subroutine arguments
by sundialsvc4 (Abbot) on Mar 15, 2011 at 17:03 UTC

    My customary strategy, in cases where there are a variable number of arguments, is to pass a single hashref, instead.   I have a standard subroutine which is given the argument-list, the subroutine name, and a list(ref) of expected parameters.   If any of the key-names in the list are not in the argument hashref, it confesses.   (see Carp)

    About the only thing I can say about “long parameter lists” is that you get ’em wrong, and you can’t easily see what you did.   That leads to a lot of silly-bug debugging time.   (a.k.a. “whack-a-head” doh!-bugging, which I thoroughly despise.)

Re: Omitted subroutine arguments
by TomDLux (Vicar) on Mar 15, 2011 at 19:17 UTC

    The traditional idiom is to have optional parameters at the end of the list. If there's only one optional arg, then either is is present or it isn't, which works out fine. The variable inside the subroutine will be undef, which you can detect and replace with an empty string, if necessary.

    But if you're going to have several optional parameters, then you are forced to have undef arguments in the subroutine call, which is ugly. Worse, if the optional argument has a value of '1', it's hard to read which option is present. If you're forced to do this with old code, use values that read well on the calling side, if possible.

    #no so good createIndex( $table, $name, $keyfield, undef, undef, 1); createIndex( $table, $name, $keyfield, undef, 75); #slightly better createIndex( $table, $name, $keyfield, undef, undef, 'ignore_dup_keys' + ); createIndex( $table, $name, $keyfield, undef, 'fillfactor=75' );

    If you have control, and there are optional element, it's best to use a hashref. Even if all the args are necessaary, if the number of elements goes over 3 or 4, a hashref is a good idea to improve readability.

    As Occam said: Entia non sunt multiplicanda praeter necessitatem.

Re: Omitted subroutine arguments
by tel2 (Pilgrim) on Mar 15, 2011 at 21:02 UTC
    Thank you all for your most excellent answers!
Re: Omitted subroutine arguments
by mincus (Chaplain) on Mar 16, 2011 at 18:12 UTC
    I do just about the same as the replies above, almost all of my subroutines look like
    sub my_sub { my %args = ( param1 => 'default', param2 => 0, @_, ); ... }
    Then I call it like
    my_sub( param1 => 'Not Default Text' );
    then the defaults get overridden with what I send, or use the defaults if I don't. If it is an object method, then I just add a line before the args:
    sub my_sub { my $self = shift; my %args = ( @_, ); .... }
Re: Omitted subroutine arguments
by techcode (Hermit) on Mar 20, 2011 at 20:51 UTC

    If you really want to get funky you can do that by analyzing @_ array and possibly even it's contents and type/content of stuff in it.

    Before people say that it's bad idea - I agree that it usually is a bad idea - but sometimes it just makes sense. Like one module I'm working on - in which I'm trying to avoid as much of additional code that you have when working with DB - but allowing you a lot of customization - and not taking you away from SQL in the usual ORM way.

    sub get { my $self = shift; my $columns = ref $_[0] eq 'ARRAY' ? join(', ', @{ shift() }) : ' +*'; my $db_table = shift || croak "I really need to know DB/Table name +"; my $field = @_ == 2 ? shift : 'id'; my $value = shift || croak "I really need key value"; my ($db, $table) = ($db_table =~ /^.+\..+$/) ? split(/\./, $db_tab +le) : ($self->default_db(), $db_table); my $dbh = $self->dbh($db); my $sth = $dbh->prepare_cached("SELECT $columns FROM $table WHERE +$field = ?", undef, 2); $sth->execute($value); my $resp = $sth->rows() ? $sth->fetchrow_hashref() : undef; return $resp; }
    This allows you to call this method using any of these ways:
    $object->get('tablename', 10); $object->get('dbname.tablename', 10); $object->get([qw/id foo bar/], 'tablename', 10); $object->get('tablename', 'key_field', 10); $object->get([qw/id foo bar/], 'dbname.tablename', 'key_field', 10);

    Yes I could have used hash(ref) based params - but I wanted to avoid them because that would add what I found as unnececary cruft - that I am trying to avoid in the first place - looking something like:

    $object->get( fields => [qw/id foo bar/], database => 'dbname', table => 'tablename', key_field => 'key_field', key_value => 10, );

    And to repeat, in general it's a bad idea, but IMHO in this case it does make sense as it replaces a familiar structure of SELECT _fields_ FROM _dbname_._tablename_ WHERE _id_field_ = _id_value_; (added bonus that DB can be a different connection/DBH) so it's relatively easy to remeber ordering and uses the most usual defaults if you ommit them.

    In your case it could be something like:

    sub test1 { my $arg1 = shift; # but if you wanna print $arg2 then use '' instead of undef ot +herwise you get warning # and you do have use strict; use warnings; at the top right? +:) my $arg2 = @_ >= 2 || ref $_[0] ne 'ARRAY' ? shift : undef; my $argrest = shift; print "arg1=$arg1, arg2=$arg2, argrest=@$argrest\n\n"; } test1('arg1', [qw/foo bar/]); test1('arg1', \@otherargs); test1('arg1', 'arg2'); test1('arg1', 'arg2', [qw/foo bar/]);


    Have you tried freelancing/outsourcing? Check out Scriptlance - I work there since 2003. For more info about Scriptlance and freelancing in general check out my home node.
      Thanks for taking the time to share that, techcode.
      Just noticed it now.
      Tel2

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (7)
As of 2024-04-19 09:05 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found