Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight

How to pass two lists to a sub?

by YAFZ (Pilgrim)
on Nov 05, 2004 at 11:30 UTC ( [id://405463]=perlquestion: print w/replies, xml ) Need Help??

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

Dear monks,

I want to pass a few scalars and *two* lists two a sub. As far as I can see my initial attempts at using @_ in a simple sense didn't work. Here is my code snippet:
sub process_something { my $s1; my $s2; my $l1; my $l2; ($s1, $s2, @l1, @l2) = @_; . . . } process_something($scalar1, $scalar2, @list1, @list2);
The problem is that when the sub is called and the first assignment is made, @l1 grabs all the elements of both @list1 and @list2.

How can I pass two consecutive lists to my sub and have them assigned to two different list variables in a correct manner? I've searched the PerlMonks site but just found an example with hashes which confused me a lot. All I need is a simple construct in which I'll have two correct @l1 and @l2 at the end.

Thanks in advance.

Replies are listed 'Best First'.
Re: How to pass two lists to a sub?
by gothic_mallard (Pilgrim) on Nov 05, 2004 at 11:42 UTC

    Pass them by reference:

    process_something($s1, $s2, \@l1, \@l2);

    Check out perlref for an explanation on references.

    --- Jay

    All code is untested unless otherwise stated.
    All opinions expressed are my own and are intended as guidance, not gospel; please treat what I say as such and as Abigail said Think for yourself.
    If in doubt ask.

Re: How to pass two lists to a sub?
by davorg (Chancellor) on Nov 05, 2004 at 11:52 UTC

    It is impossible to pass two lists to a subroutine. And there is no such thing as a "list variable" in Perl.

    I suspect that you are confusing arrays and lists. It is possible to pass two arrays to a subroutine and you do that by passing references to the arrays as others have pointed out.

    The difference between arrays and lists is an important distinction to make. Failing to make that distinction will only lead to a lot of confusion.


    "The first rule of Perl club is you do not talk about Perl club."
    -- Chip Salzenberg

Re: How to pass two lists to a sub?
by Prior Nacre V (Hermit) on Nov 05, 2004 at 12:06 UTC

    To do this, pass references to the arrays and dereference in the subroutine:

    process_something($scalar1, $scalar2, \@list1, \@list2); sub process_something { my ($scalar1, $scalar2, $ra_list1, $ra_list2) = @_; my @list1 = @$ra_list1; my @list2 = @$ra_list2; }

    Another way is to pass all parameters in a hashref:

    my $rh_params = { scalar1 => $scalar1, scalar2 => $scalar2, list1 => \@list1, list2 => \@list2, }; process_something($rh_params); sub process_something { my $rh_params = shift; my $scalar1 = $rh_params->{scalar1}; my $scalar2 = $rh_params->{scalar2}; my @list1 = @{$rh_params->{list1}}; my @list2 = @{$rh_params->{list2}}; }

    The second method involves more coding but provides a number of benefits:

    • No more concerns about getting parameters in the right order
    • Any, or all, parameters can be optional with the subroutine setting defaults for missing keys
    • Add or remove parameters without changing the subroutine signature
    • It also affords a certain degree of data encapsulation

    Update: Added missing line: process_something($rh_params);



      A few things - mostly just personal preference.

      I prefer to use the "shift" function, like this:

      sub process_something { my $s1 = shift; my $s2 = shift; my $arrayref1 = shift; my $arrayref2 = shift; # And instead of creating new hashes from the # references passed in... #my @array1 = @$arrayref1; #my @array2 = @$arrayref2; # You can use the references themselves... # like $arrayref->[i] below... print "Subroutine process_something: \$s1 = $s1\n"; print " \$s2 = $s2\n"; my $count_array1_elements = @$arrayref1; for ($i = 0; $i < $count_array1_elements; $i++) { printf "Array1 Element %d = %s\n", $i, $arrayref->[i]; } } process_something($scalar1, $scalar2, \@array1, # pass a *reference* to the array \@array2); # same

        Personal coding preferences aside, I did consider something along the lines you have here but decided against it for two reasons.

        1. YAFZ asked specifically for arrays: "All I need is a simple construct in which I'll have two correct @l1 and @l2 at the end."
        2. Some benchmarking I carried a few months ago indicated that evaluating $array[$i] was faster than evaluating $arrayref->[$i]. (As one might expect given the additional dereference operation.)

        (Minor point: You have $arrayref->[i] in a number of places.)



        I prefer to use the "shift" function

        I think using shift to pull in arguments is occasionally warranted, but it should be avoided most of the time. My reasons are as follows:

        • It's less maintainable. To add a new argument, you either have to copy and paste the shift line, and modify the variable name, or you have to retype essentially the same thing each time. These are both error-prone processes. Error-prone processes should be avoided whenever possible.
        • Each shift modifies the @_ array. My concern with this is not about performance (see a related post), but about semantics. Sometimes it makes sense to modify the array, and in those cases I use shift. But usually you are just trying to copy the arguments into more convenient variable names, and that does not necessitate the destruction of the array.

        Accessing @_ directly does not have these problems. Using the common construction my ($foo, $bar, $baz) = @_; makes it easy to add new arguments, and it does not mangle @_ as a side-effect. I would be interested to hear your reasons in favor of shift-by-default, because, to me, the reasons against it are pretty convincing.

Re: How to pass two lists to a sub?
by tinita (Parson) on Nov 05, 2004 at 11:34 UTC
    from perlsub:
    Do not, however, be tempted to do this: (@a, @b) = upcase(@list1, @list2); Like the flattened incoming parameter list, the return list is also flattened on return. So all you have managed to do here is stored everything in @a and made @b an empty list. See "Pass by Reference" for alternatives.
    this advice should also fit for your problem.
Re: How to pass two lists to a sub?
by YAFZ (Pilgrim) on Nov 05, 2004 at 12:23 UTC
    davorg, I'm really sorry for the confusion, now the distinction between a list and an array is much clearer to me, thanks for enlightening! :)

    Prior Nacre V, thanks a lot for the lengthy explanation and showing a very good method to pass the variables in a hash which provides the flexibility I was just looking for. Looks like I got this explanation as a bonus ;-)
Re: How to pass two lists to a sub?
by johnnywang (Priest) on Nov 05, 2004 at 17:08 UTC
    You can pass two arrays without flattening them or references if you use function prototypes:
    sub foo(@@){ }
    Many build-in functions (e.g., push) has two list parameters. Read about it in the docs: type "perldoc perlsub"
      Almost, but not quite.
      sub foo(\@@) { my ($aref,@rest) = @_; }
      is the prototype for push: push does not have two list parameters, because you cannot have more than one list parameter; it has an array parameter, which automatically gets passed as an array reference, followed by a list (the rest of the arguments).

      If you really want to use prototypes for two arrays you should use \@\@, optionally followed by more arguments:

      #!/usr/local/bin/perl -w use strict; my @a = qw(a b c d); my @b = qw(e f g h); my @c = qw(i j k l); sub test(\@\@@) { my ($a,$b,@rest) = @_; print "@$a\n@$b\nrest is @rest\n"; } test(@a,@b,@c); __END__ a b c d e f g h rest is i j k l

      update: I should probably make clear that using prototypes in Perl is generally frowned upon except in exceptional circumstances. There are good reasons for this attitude, but let me just point out that in the above example you can accomplish the same result if you pass the arrays by reference explicitly, and it's shorter to create anonymous arrayrefs than to pass two arrays if you don't intend to use the arrays any other way:

      test( [qw(a b c d)], [qw(e f g h)], qw(i j k l)); sub test { my ($a,$b,@rest) = @_; print "@$a\n@$b\nrest is @rest\n"; }
      update2: fixed typo

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://405463]
Approved by tinita
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others examining the Monastery: (6)
As of 2024-04-15 13:37 GMT
Find Nodes?
    Voting Booth?

    No recent polls found