views:

119

answers:

8

I have been working with perl for about two months now; it just occurred to me that I don't know how to set default arguments for subroutines. Here is what I considered:

sub hello {
  print @_ || "Hello world";
}

And that works fine for if all you needed was one argument. How would you set default values for multiple arguments? I was going to do this:

sub hello {
  my $say = $_[0] || "Hello";
  my $to  = $_[1] || "World!";
  print "$say $to";
}

But that's a lot of work... There must be an easier way; possibly a best practice? Thanks!

+2  A: 

There's the Attribute::Default module on CPAN. Probably cleaner than this, and avoids a couple of complexities (such as, what if you want to pass false to your subroutine?).

I've also seen people use my $var = exists @_[0] ? shift : "Default_Value";, but Perl's documentation notes that calling exists on arrays is deprecated, so I wouldn't really recommend it.

Snippet of Attribute::Default from the doc page:

  sub vitals : Default({age => 14, sex => 'male'}) {
     my %vitals = @_;
     print "I'm $vitals{'sex'}, $vitals{'age'} years old, and am from $vitals{'location'}\n";
  }

   # Prints "I'm male, 14 years old, and am from Schenectady"
   vitals(location => 'Schenectady');
eldarerathis
+4  A: 

I usually do something like:

sub hello {
    my ($say,$to) = @_;
    $say ||= "Hello";
    $to ||= "World!";
    print "$say $to\n";
}

Note that starting from perl 5.10, you can use the "//=" operator to test if the variable is defined, and not just non-zero. (Imagine the call hello("0","friend"), which using the above would yield "Hello friend", which might not be what you wanted. Using the //= operator it would yield "0 friend").

jsegal
Yea I tried `print @_ // "Hello world";` but I got `0`...
David
@Davidmoreen: testing an array for definedness isn't useful
ysth
@Davidmoreen, the correct way to say that is `print $_[0] // "Hello world";`. As ysth said, you can't use `defined` on an array (which is what `//` means), because an array in scalar context returns its length, which is always defined.
cjm
+1  A: 

Because Perl's mechanism for passing arguments to subroutines is a single list, arguments are positional. This makes it hard to provide default values. Some built-ins (e.g. substr) handle this by ordering arguments according to how likely they are to be used -- less frequently used arguments appear at the end and have useful defaults.

A cleaner way to do this is by using named arguments. Perl doesn't support named arguments per se, but you can emulate them with hashes:

use 5.010;  # for //

sub hello {
    my %arg = @_;
    my $say = delete $arg{say} // 'Hello';
    my $to  = delete $arg{to}  // 'World!';
    print "$say $to\n";
}

hello(say => 'Hi', to => 'everyone');  # Hi everyone
hello(say => 'Hi');                    # Hi world!
hello(to  => 'neighbor Bob');          # Hello neighbor Bob
hello();                               # Hello world!

Note: The defined-or operator // was added in Perl v5.10. It's more robust than using a logical or (||) as it won't default on the logically false values '' and 0.

Michael Carman
+8  A: 

I do it with named arguments like so:

sub hello {
    my (%arg) = (
        'foo' => 'default_foo',
        'bar' => 'default_bar',
        @_
    );

}

I believe Params::Validate supports default values, but that's more trouble than I like to take.

ysth
A: 

If you see the documentation of Perl Best Practices: Default argument Values by Damian Conway then you will find some important points like:

  • Resolve any default argument values as soon as @_ is unpacked.
  • It suggest that if you have many default values to set up then the cleanest way would be factoring out the defaults into tables ie., a hash and then preinitializing the argument hash with that table.

Example:

#!/usr/bin/perl
  use strict;
  use warning;
  my %myhash = (say => "Hello", to => "Stack Overflow");
  sub hello {
   my ($say, $to) = @_;
   $say =  $say ? $say : $myhash{say};
   $to =  $to ? $to : $myhash{to};
   print "$say $to\n";
  }
  hello('Perl');      # output :Perl Stack Overflow
  hello('','SO');     # output :Hello SO
  hello('Perl','SO'); # output :Perl SO
  hello();            # output :Hello Stack Overflow

For more detail and complete example refer Perl Best Practices.

Nikhil Jain
+2  A: 

Also have a look at Method::Signatures. This uses Devel::Declare to provide some extra (needed!) sugar with the keywords method and func.

Below is your example using the new func:

use Method::Signatures;

func hello ($say='Hello', $to='World!') {
    say "$say $to";
}

hello( 'Hello', 'you!' );    # => "Hello you!"
hello( 'Yo' );               # => "Yo World!"
hello();                     # => "Hello World!"

/I3az/

draegtun
A: 

The best way to address your problem have been discussed in the other answers.
One thing that strikes me though is that you state that:

sub hello {
   print @_ || "Hello world";
}

And that works fine for if all you needed was one argument.

Have you actually tried that code? It will print the number of arguments or, when none provided, Hello World!
The reason for this is that the ||-operator takes precedence and forces the left-hand side in scalar context, thus reducing @_ to the number of arguments you provide, NOT the arguments itself!
have a look at perlop for more information on operators in Perl.

HTH,
Paul

pavel
A: 

For more sugar, see also Method::Signatures:

func add($this = 23, $that = 42) {
    return $this + $that;
}
pwes
Sorry, didn't notice it was already proposed
pwes