21

I want to pass several parameters, one of which is optional, to a function. The only way to do it that I know is using a list (@) as a parameter. Thus, it contents nothing or 1 element (will never be undef), so that I can use the following code:

sub someFunction($$@) {
    my ( $oblig_param1, $oblig_param2, $option_param ) = @_;
    ...
} 

This code works, but I feel that maybe it's not the best workaround.
Are there any other ways to do it?
Thank you.

TLP
  • 66,756
  • 10
  • 92
  • 149
evgeny9
  • 1,381
  • 3
  • 17
  • 31
  • 3
    Are you sure you need the prototype? – TLP Nov 14 '11 at 15:52
  • TLP, we are using prototypes as it is more demonstrative. – evgeny9 Nov 14 '11 at 16:23
  • 4
    In addition to TLP answer take a look at http://stackoverflow.com/questions/297034/why-are-perl-5s-function-prototypes-bad/297265#297265 – Matteo Nov 14 '11 at 16:24
  • 14
    I disagree. Prototypes will only serve to confuse things. If you want clarity, add a comment. – TLP Nov 14 '11 at 16:30
  • **Matteo**, thank you for the link: a great explanation there. – evgeny9 Nov 14 '11 at 16:34
  • **TLP**, I'm not much of a Perl programmer so far. Prototypes are a bit confusing after C/C++. But with your detailed answers learning Perl won't take me so long :-) – evgeny9 Nov 14 '11 at 16:38
  • @evgeny9 StackOverflow is a great place to learn new things. =) I learn new things most every day, and I did not think that was possible. – TLP Nov 14 '11 at 16:39
  • @TLP, yes, now I see. But at first, I just didn't expect such a bunch of qualified and fast answers! – evgeny9 Nov 14 '11 at 16:42

3 Answers3

42

Prototypes (the ($$@) part of your sub declaration) are optional themselves. They have a very specific use, and if you don't know what it is, it is better to not use it. From perlsub:

...the intent of this feature is primarily to let you define subroutines that work like built-in functions

Just remove the prototype from your sub declaration, and you can use whatever arguments you like.

sub someFunction {
    my ( $oblig_param1, $oblig_param2, $option_param ) = @_;
    if (defined $option_param) {
        # do optional things
    }
    $option_param //= "default optional value";
    ....
} 
TLP
  • 66,756
  • 10
  • 92
  • 149
  • thank you. So, if I'm writing a Perl module, that'll be used by my colleagues, I should use prototypes? – evgeny9 Nov 14 '11 at 16:28
  • 3
    Like I said in my comment, if you want clarity, add a comment instead. Don't use prototypes unless you specifically want the functionality that prototypes provide. – TLP Nov 14 '11 at 16:33
  • 10
    Usually, you shouldn't! See [Far More Than Everything You've Ever Wanted to Know about Prototypes in Perl](http://www.perlmonks.org/?node_id=861966) – salva Nov 14 '11 at 16:52
20

You can use a semicolon in the prototype to indicate the end of the required parameters:

sub someFunction($$;$) {
  my ( $oblig_param1, $oblig_param2, $option_param ) = @_;
  ...
}

The ; is optional before a @ or %, which, according to the docs, "gobbles up everything else".

EDIT: As DVK points out in a comment (and TLP emphasizes in another answer here), you are probably best off simply avoiding prototypes:

sub someFunction {
  my ( $oblig_param1, $oblig_param2, $option_param ) = @_;
  ...
}

Perl prototypes have their uses (mostly to supply implicit context coercion to arguments, as Perl's built-in functions do). They should not be used as a mechanism to check that function are called with the correct number and type of arguments.

Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
  • Would the downvoter care to explain what was objectionable about this answer? – Ted Hopp Nov 14 '11 at 17:05
  • 14
    @Ted - I didn't DV, but my best guess is that because you omitted the most important sentence required for a good answer involving prototypes: "If you **need** to write a function that acts like a built-in, use a prototype. Otherwise, **don't use prototypes**" http://stackoverflow.com/questions/297034/why-are-perl-5s-function-prototypes-bad/297265 – DVK Nov 14 '11 at 17:43
  • 1
    @DVK - fair enough. A downvote without a reason doesn't really help anyone. The reason you offer makes sense. – Ted Hopp Nov 14 '11 at 18:04
16

It is a good idea to group parameters in a $parameter hashref. This is especially useful if several options (mandatory or optional) need to be provided.

To access any parameter, simply use $parameter->{oblig1} or $$parameter{option2}.

Passing hashrefs make it especially convenient when developing, so when the need for $oblig3 comes along, the ordering of the arguments changes neither at the caller nor the sub itself. Compare before and after:


# BEFORE $oblig3

--------------------------+-------------------------
# Caller                  | # Sub
--------------------------+-------------------------
someFunc( $oblig1,        | sub {
          $oblig2,        |   my ( $oblig1,
          $option1 );     |        $oblig2,
                          |        $option1 ) = @_;
                          | }
--------------------------+-------------------------

# AFTER $oblig3

--------------------------+-------------------------
# Caller                  | # Sub
--------------------------+-------------------------
someFunc( $oblig1,        | sub {
          $oblig2,        |   my ( $oblig1,
          $oblig3,        |        $oblig2,
          $option1 );     |        $oblig3,
                          |        $option1 ) = @_;
                          | }
--------------------------+-------------------------

The argument order changes at both caller and sub, so order needs to be maintained and respected.

Using hashrefs, there is no need to worry about argument order:

--------------------------+-------------------------
# Caller                  | # Sub
--------------------------+-------------------------
someFunc({ oblig1  => 1   | sub {
           oblig2  => 2   |   my ( $params ) = @_;
           option1 => 1   |   # No changes to    
           oblig3  => 7   |   # argument passing
         });              |  }    
                          | 
--------------------------+-------------------------

Depending on the design needs of the subroutine, the following subroutine argument patterns could be utilized:

  1. my ( $mandatory_parameters, $optional_parameters ) = @_;

    This pattern is useful if there are several of each. The beauty of this approach is that $optional_parameters is undefined if not passed, so the default case could be executed if ! $optional_parameters;

    Note that the mandatory parameters will need to be checked subsequently:

    for ( qw/ a b c / ) { 
        die "Missing '$_' parameter\n"
          unless exists $mandatory_parameters->{$_};
    }
    
  2. my ( $parameters ) = @_;

    Useful if there are few or no mandatory parameters.

    It is also extremely effective if parameters are passed to simply modify default behavior. By defining $default_parameters in the scope of the package, the defaults can be loaded by a subsequent one-liner unless a parameter was explicitly passed:

    $parameters = { %$default_parameters, %$parameters };

Zaid
  • 36,680
  • 16
  • 86
  • 155
  • **Zaid**, thank you, but I think that using hash links is a bit early for me. – evgeny9 Nov 14 '11 at 16:45
  • 1
    @evgeny9 : If you're willing to play around with prototypes, I suggest you take the time to understand hashes in a bit more detail. It's not that difficult, trust me! :) – Zaid Nov 14 '11 at 16:52