Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Correct idiom for default parameters

by mrider (Beadle)
on Apr 28, 2010 at 17:29 UTC ( [id://837356]=perlquestion: print w/replies, xml ) Need Help??

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

Greetings monks:

I have an object which is designed to have default values for parameters if they are not supplied. I am currently using the following style, but I can't help but feel that my solution is not very "Perl-like".

#!usr/bin/perl -w use strict; #Demonstrate and test usage my $test = Test::new(); $test->defaults(); $test->defaults(0); $test->defaults(0, 0); $test->defaults(0, 0, 0); package Test; sub new { my $self = {}; bless($self); return $self; } #This is the pertinent part... sub defaults { my ($self, $default1, $default2, $default3) = @_; $default1 = 1 if($#_ < 1); #Is there a better way to do this? $default2 = 2 if($#_ < 2); $default3 = 3 if($#_ < 3); print("$default1 $default2 $default3\n"); } 1;

EDIT Well, as I say in reply to AR I only have version 5.8.8 available on the workstation where this runs. As an interesting aside, my Debian Stable server has 5.10.0. :) Anyway, I guess what I'm doing isn't so bad.

EDIT 2 I should probably clarify further: I don't really intend to have long parameter lists. If I did, I would use mapped parameters. Or better yet I would find a way to not do that in the first place. My problem is that I'm having a difficult time finding a nice way to tell the difference between the user sending two parameters, the last of which is 0, and the user sending one parameter wanting me to use the default second parameter (which is typically 1 for "Yes"). There is one place that I found where the code I'm modifying takes two parameters, and the second one has reasonable default. It's particularly irksome if the first parameter ("self" notwithstanding) is 0, because then I wind up mucking up the whole works.

Replies are listed 'Best First'.
Re: Correct idiom for default parameters
by MidLifeXis (Monsignor) on Apr 28, 2010 at 17:43 UTC

    If you only have one layer of parameters (an HoV?), you could do this....

    sub foo { my %defaults = ( a => 1, b => 2, c => 3, ); # other forms left as exercise... my %params = (%defaults, @_); # ... }

    --MidLifeXis

      Thanks for that, although that doesn't quite do what I'm hoping. I'm not necessarily looking for named parameters, and it makes calling the code more difficult than I wanted.

        It also makes the calling code self-documenting. This is a large win for anyone dealing with the code including you when you come back to it a couple months later and can't remember which of the 6 args goes in the middle or what happens if you leave arg 2 out, etc, etc.

Re: Correct idiom for default parameters
by Your Mother (Archbishop) on Apr 28, 2010 at 18:11 UTC

    Test::new() is pretty un-Perl/OO way to get an object made. Object constructors are generally called as class or objects methods: Test->new([args]). Then you'd have an _init methods to process the args to new or later in other calls where they are allowed. I hope you are not really using the name "Test" for your package. :)

    I find >2 arguments to be the tipping point for making named arguments much preferable to positional. What MidLifeXis showed is a good way to go about that.

      Good points. Understand though, that this bears almost no resemblance to my "real" code. I was attempting to put together the absolute minimum necessary to demonstrate the question and my intentions. And no, my real package isn't called "Test" :)

      For the most part, the sub routines in question take one parameter and it's optional. I think there's one (maybe two) subroutines that take two where the first is mandatory, and the second is optional. I'd have to look again, as I forget off the top of my head. I do appreciate the input though.

      Object constructors are generally called as class or objects methods Test->new([args])
      There's only one object constructor in Perl, and it's called bless. Test->new is nothing more than a class method, that just happens to return an object, often of the same class. More often than not, programmers decide said new() creates a brand-spanking new object, which gets initialized as well. These programmers make it hard to do MI.

        I know. I was guessing that was understood though perhaps being so explicit would have been helpful here for the OP.

        I don't understand ...

        Imho it's common and accepted terminology to call methods returning member objects "constructors"

        --lanx

Re: Correct idiom for default parameters
by AR (Friar) on Apr 28, 2010 at 17:37 UTC

    This assumes 5.10 or higher:

    sub defaults { my $self = shift; my $default1 = shift // 1; my $default2 = shift // 2; my $default3 = shift // 3; print("$default1 $default2 $default3\n"); }

    Though I would never claim this is the "correct" idiom. Celebrate TIMTOWTDI!

      Ack! 5.8.8 here (sigh).

      Thanks for the suggestion though.

        This is 5.8 accessible and might even be more palatable?

        Updated per LanX's correction below.

        sub test{ my( $p1, $p2, $p3) = map{ scalar @_ ? shift : $_ }( 1, 2, 3 ); print "p1:$p1; p2:$p2; p3:$p3"; };; test();; p1:1; p2:2; p3:3 test( 'a' );; p1:a; p2:2; p3:3 test( 'a', 'b', 'c' );; p1:a; p2:b; p3:c

        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Correct idiom for default parameters
by BrowserUk (Patriarch) on Apr 28, 2010 at 17:44 UTC

    Here's one way (requires 5.10+). You'll decide for yourself if it is any better:

    sub test{ my $p1 = shift // 1; my $p2 = shift // 2; my $p3 = shift // 3; print "p1:$p1; p2:$p2; p3:$p3"; };; test();; p1:1; p2:2; p3:3 test( 'a' );; p1:a; p2:2; p3:3 test( 'a', 'b' );; p1:a; p2:b; p3:3 test( 'a', 'b', 'c' );; p1:a; p2:b; p3:c

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

      What about the more obvious following?

      sub test{ my $p1 = shift || 1; [...]

        Suffers because you cannot set $p1 explicitly to 0 (or '' or undef). That's why 5.10 has //.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Correct idiom for default parameters
by LanX (Saint) on Apr 28, 2010 at 20:22 UTC

    if you are restricted to positional parameters, what about this ...

    sub defaults { my $d1= ( @_ ? shift : 1 ); my $d2= ( @_ ? shift : 2 ); my $d3= ( @_ ? shift : 3 ); print("$d1 $d2 $d3\n"); } defaults(); defaults(0); defaults(0, 0); defaults(0, 0, 0); __DATA__ 1 2 3 0 2 3 0 0 3 0 0 0

    Cheers Rolf

    UPDATE: precedence rules allow to omit the parens.

    UPDATE from the example you gave I assumed that you don't plan to pass undef in the middle of parameters and expect this to be checked... otherwise all these solutions are too simplified and you have to check explicitly with defined. But in this case I really recommend using named parameters!

      more condensed:

      sub defaults { my ($d1,$d2,$d3) = map { @_ ? shift : $_ } (1,2,3); print("$d1 $d2 $d3\n"); } defaults(); defaults(0); defaults(0, 0); defaults(0, 0, 0);

      plz note: this solution depends on the number of defaulted parameters...

      Cheers Rolf

        That looks pretty good!

        And to answer your question in the post previous to this one, no I don't expect there to be an "undef" in the middle. The overwhelming majority of the cases there is one parameter that is optional. Which means that the predominant use will look like this:

        sub defaults { my ($self, $param) = map { @_ ? shift : $_ } (1); print($param); }

        I found one place where it would be nice to have two optional parameters, but I'm debating refactoring that sub routine.

      for completeness:

      you can even slice lists ...

      sub defaults { my ($d1,$d2,$d3) = ( @_ , (1,2,3)[@_ .. 2] ); print("$d1 $d2 $d3\n"); } defaults(); defaults(0); defaults(0, 0); defaults(0, 0, 0);

      but I don't like that the maximum index has to be hard coded.

      at least this solution doesn't destroy @_ ...

      Cheers Rolf

DWIM code would use Params::Validate
by metaperl (Curate) on Apr 29, 2010 at 17:08 UTC

      You seem to be a big fan of software (over)engineering. As I've only seen overengineered solutions, maybe you can show an example where the code using Params::Validate is shorter and/or more concise or even more readable than the solutions already presented. The synopsis of Params::Validate only shows verbose "solutions" spanning at least five or six lines.

      Here's one reason not to use Params::Validate.

      The code (minus the inline stuff), in Re: Inline Subs Revisited takes just over 4 1/2 minutes to run.

      Modify those subs to validate their parameters using P:V like so:

      sub max { validate_pos( @_, { TYPE => ARRAYREF } ); my ($list) = @_; my $max; for (@$list) { $max = $_ if ! defined $max || $_ > $max; } return $max; }

      And it takes almost 22 minutes.

      And achieves nothing that couldn't be done with die unless ref( $list ) eq 'ARRAY';. (That costs nothing measurable.)

      Its like trying get better fuel economy by adding a large, copper-wound electric motor and 50Kg of batteries to your car.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
        You can disable validation for production to avoid the hit, set VALIDATION to something true
      A reply falls below the community's threshold of quality. You may see it by logging in.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others chilling in the Monastery: (4)
As of 2024-04-18 01:39 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found