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

Hi Monks!
I have a question here regarding values coming into a sub routine. I have this external module and I share it with about 4 other programs I have. I call this sub routine in this module from all these 4 programs, lets say;
From program A:
build_url( $account ,$transaction, $location);
From prgram B:
build_url( $account);
From program C:
build_url( $account ,$transaction);
From program D:
build_url( $account ,$transaction, $location);
Now this is what the sub rutine inside of the mudole is expecting:
sub build_url { my ($account ,$transaction, $location) = @_; ....

Now, I need to pass 2 extra values into this sub routine, the new values expected inside of this sub routine will be like this:
sub build_url { my ($account ,$transaction, $location, $fname, $lname) = @_; ....

So my question here is what is the best way to do this to avoid the values of these 2 extra variables added to the call to this sub routine when the values are not in the order that the sub “build_url” is expecting?
Here is how the 2 new variables will be added to the call to this sub.

From program A:
build_url( $account ,$transaction, $location, $fname, $lname);
From prgram B:
build_url( $account, $fname, $lname);
From program C:
build_url( $account ,$transaction, $fname, $lname);
From program D:
build_url( $account ,$transaction, $location, $fname, $lname);
Now the sub build_url with the 2 extra variable added:
sub build_url { my ($account ,$transaction, $location, $fname, $lname) = @_; ....

See where some of the values would be in the wrong order? Should I make sure that I always pass 5 expected values to this sub routine even if I have to pass blank values, but preserving the order that the code is expecting them?
Thanks for the advice!

Replies are listed 'Best First'.
Re: Values passed into a sub routine
by toolic (Bishop) on Apr 19, 2011 at 16:02 UTC
    Since it looks like you will be modifying your module and all your calling programs, consider passing your values as a hash reference. This is a common practice. It has the benefits of scalability and order-independence. It would be more verbose, but that's a good thing because it would be passing parameters by name, and therefore more readable. See: Pass by Reference. Here is one CPAN module which shows this technique; you could look at its source code.

    Also, there are parameter checking modules, such as Params::Validate and Params::Check (Core).

Re: Values passed into a sub routine
by Nikhil Jain (Monk) on Apr 19, 2011 at 16:01 UTC

    use named Parameter passing concept like

    build_url(account => "test",transaction => "test1", location => "India +", fname => "Nikhil", lname => "jain"); build_url(account => "test", fname => "nikhil", lname => "jain"); build_url(account => "test" ,transaction => "test1", fname =>"nikhil", + lname => "jain"); sub build_url { my (%args) = @_; my $fname = $args{fname} || "Bob the Builder"; my $lname = $args{lname} || "none that we know"; }
      This is the way to go imho. Remove the $ in the parameter names though (so account => "test" instead of $account => "test") I like this idiom:
      sub build_url { my %param = ( fname => "default value", ..., @_ ); ... # do stuff with $param{account} etc. }
Re: Values passed into a sub routine
by ikegami (Patriarch) on Apr 19, 2011 at 16:03 UTC

    If I follow, the syntaxcalling convention is

    build_url($account, [ $transaction, [ $location, ] ] $fname, $lname);

    where square brackets denote optional parameters. You could use

    sub build_url { my $lname = pop; my $fname = pop; my ($account, $transaction, $location) = @_; ... }
    or
    sub build_url { my $account = shift; my $transaction = @_>=4 ? shift : undef; my $locaton = @_>=3 ? shift : undef; my $fname = shift; my $lname = shift; ... }
    or
    sub build_url { splice(@_, 1, 0, (undef)x(5-@_)) my ($account ,$transaction, $location, $fname, $lname) = @_; ... }
      I like this way as you stated, but:
      build_url( $account,[$fname], [$lname]); sub build_url { my $lname = pop; my $fname = pop; print "$lname and $fname\n"; my ($account ,$transaction, $location) = @_; ... }

      If a run this I am getting this:
      Lname=ARRAY(0x8967f28) and Fname=ARRAY(0x8967748)
      Any suggestion? Thanks!

        To put it differently, I understood that you want to support the following three calling conventions and only those three calling conventions:

        build_url($account, $fname, $lname); build_url($account, $transaction, $fname, $lname); build_url($account, $transaction, $location, $fname, $lname);

        If so, all three alternatives I provided should work.

        What about this:
        build_url( account => $account, fname => $fname, lname => $lname], ); sub build_url { my (%args) = @_; my $account = $args{account} || ''; my $fname = $args{fname} || ''; my $lname = $args{lname} || ''; my $other = $args{other} ||''; ... }

        There, it should prevent it to be out of order right?
Re: Values passed into a sub routine
by wallisds (Beadle) on Apr 19, 2011 at 15:59 UTC

    In my opinion the answer to your question is 'yes'. At least that's what I'd do. I'd probably just pass 0 through... In the sub I would then check for the value before proceeding.

    From program A:
    build_url( $account ,$transaction, $location, $fname, $lname);
    From program B:
    build_url( $account, 0, 0, $fname, $lname);
    From program C:
    build_url( $account, $transaction, 0, $fname, $lname);
    From program D:
    build_url( $account ,$transaction, $location, $fname, $lname);

    sub build_url { my ($account ,$transaction, $location, $fname, $lname) = @_; if ($location) { ... do what you would do ... }#example }
      What if '0' is valid for one of the entries?!? Better to use 'undef' instead. And even better would be to just pass a hash reference in (or use named parameters).

      Elda Taluta; Sarks Sark; Ark Arks

        Thanks Argel!

        I appreciate what you have posted and I see your point. I think using undef makes sense when used also with 'defined', but here's my reasoning behind my original suggestion...

        The question was:
        "Should I make sure that I always pass 5 expected values to this sub routine even if I have to pass blank values, but preserving the order that the code is expecting them?"

        The answer is yes to that question if the person asking is going to continue to use a list array method. I think Undef is only safer than 0 if you use 'defined' to check it, otherwise it's the same as 0, '', '0', right? At least that's what I got out of this perldoc: http://perldoc.perl.org/functions/defined.html

        Also, I thought 0 would be alright in this scenario based on what I interpreted the expected values to contain, but then again, it would be a mistake to assume data would be entered in any expected format, especially if it is coming in from a form.

        If we don't use a hash this is a better solution:
        build_url( $account, $transaction, undef, $fname, $lname);
        sub build_url() { ... etc... if (defined($location)) { ... stuff ... } }


        Thanks!
        Dawn
Re: Values passed into a sub routine
by lostjimmy (Chaplain) on Apr 19, 2011 at 18:28 UTC
    As Nikhil Jain and toolic have said, named parameters are a great way to go. On the other hand, since I'm a C++ programming by trade, I'm inclined to just put the optional parameters at the end of the list instead of in the middle. That way you only have to check if they are undef and clients don't have to worry about putting in placeholders.
Re: Values passed into a sub routine
by patcat88 (Deacon) on Apr 20, 2011 at 00:29 UTC
    you can also have the first parameter control whether to interpret the rest of @_ as @_ or $_[0] as a hashref, for example,
    sub func { my($param1, $param2, $param3) = @_; my $tempparam1; if(ref($param1) eq "HASH") { $tempparam1 = $param1->{'param1'}; $param2 = $param1->{'param2'}; $param3 = $param1->{'param3'}; $param1 = $tempparam1; } print "param1 is $param1 param2 is $param2 param3 is $param3 \n"; } func( 'one', 'two', 'three'); func( {param1 => 'one', param2 => 'two', param3 => 'three'});
    I've seen the above in Perl. Offers users TIMTOWTDI.