Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Passing multiple data types to a subroutine

by Hagbone (Monk)
on Nov 28, 2003 at 19:14 UTC ( [id://310766]=perlquestion: print w/replies, xml ) Need Help??

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

I've got a situation where I'd like to pass multiple values to a subroutine.

I've done this plenty of times in the past using something like:

CallSub($Igot,$TheBlues);
and one the receiving end, doing:
my ($Igot,$TheBlues) = @_;
The new wrinkle for me in the current situation is that I'd like to send the subroutine two different data types ... and array and a couple of scalars:
@Array = qw(one two three four); $Igot = 'whatever'; $TheBlues = 'whateverelse';
And when received on the subroutine end, I'd like to be able to isolate the scalar values from the array value.

It seems that I can't pass multiple data types to the subroutine. I could easily be wrong (due to ignorance of how it's done).

One solution that occurred to me would be to "shift" the scalars into the array, and then disassemble the business after it gets to the subroutine:

$Igot = shift(@Array); $TheBlues = shift(@Array); CallSub(@Array);
and at the subroutine:
my (@PassedArray) = @_; my $TheBlues = $PassedArray[0]; my $IGot = $PassedArray[1];
And the what's left is the array I passed to the subroutine, minus the two scalars I added.

But that sure seems like the long way around the block (and potentially easier to make mistakes when unraveling), so I'll ask what I'm hoping is a question with a how-to answer: Can multiple data types be passed to a subroutine?

Replies are listed 'Best First'.
Re: Passing multiple data types to a subroutine
by BUU (Prior) on Nov 28, 2003 at 19:39 UTC
    First off, everything you pass to a sub gets flattened to a list. Scalars, Arrays, Hashes, all turn in to a list. Which means that it's impossible to tell two arrays apart. Now for simple cases, where you just trying to pass one Array or Hash, you can just put the scalars first and shift them off:
    foo($bar,$baz,@qux); sub foo { my $bar = shift; my $baz = shift; my @qux = @_; # or my($bar,$baz,@qux)=@_; }
    But if you want to pass two distinct arrays and/or hashes, you have to resort to referenecs.
    foo(\@arr1,\@arr2); sub foo { my @arr1=@{+shift}; my @arr2=@{+shift}; }
Re: Passing multiple data types to a subroutine
by BrowserUk (Patriarch) on Nov 28, 2003 at 19:36 UTC

    You need to pass the array by reference rather than by value. This section of perlsub explains it better than I can.


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    Hooray!
    Wanted!

Re: Passing multiple data types to a subroutine
by davido (Cardinal) on Nov 28, 2003 at 19:43 UTC
    You can pass multiple datastructures (and data types) of arbitrary complexity into subs by reference. For example:

    my %hash = ( 'This' => 1, 'That' => 2, 'Other' => 3 ); my @array = ( 1, 2, 3, 4, 5 ); my $scalr = "Hello World!\n"; mytest ( \%hash, \@array, $scalr ); sub mytest { my $h_ref = shift; my $a_ref = shift; my $simple = shift; local $, = ", "; print $h_ref->{$_}, "\n" foreach keys %{$h_ref}; print $_, "\n" foreach @{$a_ref}; print $simple, "\n"; }

    Take a look at perlreftut and perlsub for a more detailed description, and clever examples.


    Dave


    "If I had my life to live over again, I'd be a plumber." -- Albert Einstein
Re: Passing multiple data types to a subroutine
by Zaxo (Archbishop) on Nov 28, 2003 at 19:58 UTC

    You have several ways to go for this. Pick one that fits what you want.

    Inside the sub, you can say

    sub CallSub { my ($IGot, $TheBlues, @Array) = @_; # or #(my $IGot, my $TheBlues, @_) = @_; # or else shift the first two in # my ($IGot, $TheBlues) = (shift, shift); # ... }
    The second two are equivalent.

    Another way is to pass in an array reference, either explicitly, or by defining CallSub with a prototype of ($$\@). In either case you would do that like this,

    sub CallSub # ($$\@) # if you choose { my ($IGot, $TheBlues, $Arrayref) = @_; # or some variation # ... }
    All these reflect a slight weakness in the design your question implies. There is a very tight binding between the definition of sub CallSub and the arguments given its use. That may be unavoidable, but the most natural subs seem to take an open list of like things. You may find that your design is better than the alternatives, but you can only benefit from examining what else you might do.

    After Compline,
    Zaxo

Re: Passing multiple data types to a subroutine
by jweed (Chaplain) on Nov 28, 2003 at 19:49 UTC
    You can try this:
    $Igot = 'whatever'; $TheBlues = 'whateverelse'; @Array = 'la la la laaaa'; CallSub($Igot, $TheBlues, @Array); sub CallSub { my $Igot = shift; my $TheBlues = shift; my @Array = @_; .... }
    While this solution is just fine and dandy, passing an array ref will preserve the sanctity of individual values much more perlishy. This is especially useful when you have to pass two arrays of varying lengths.
    Callsub($number, \@Somearray, \@Otherarray); sub Callsub { my $number = shift; my @array = @{ +shift }; my @array2 = @{ +shift }; .... }
    There were no other replies when I posted this.  Really.  :)


    Who is Kayser Söze?
Re: Passing multiple data types to a subroutine
by ysth (Canon) on Nov 28, 2003 at 19:39 UTC
    To pass a mixture of types, pass the non-scalar ones as references, e.g. CallSub(\@Array, $Igot, $TheBlues) and my ($PassedArrayRef, $Igot, $TheBlues) = @_. See perldoc perlreftut for a tutorial on using the array reference in the sub.

    You can set a prototype on your function to automatically change @Array to \@Array in the caller, but I don't recommend that unless you know what you are doing; people often misunderstand what prototypes are actually for and expect more (or less) than they actually do.

Re: Passing multiple data types to a subroutine
by jeffa (Bishop) on Nov 29, 2003 at 15:31 UTC
    In case you were thinking that this behaviour is bad, well, it's not. It's actually quite useful. If you get into a habit of passing your scalars first, and ONE list at the end, you can start doing some Lisp'ish stuff:
    recurse(split('','Hello World')); sub recurse { my ($car,@cdr) = @_; print "$car\n"; @cdr and recurse(@cdr); }
    But if you find yourself passing lots of different datatypes, then consider passing a hash instead. Just note that any values that are arrays or hashes must be passed as references:
    foo( array => [0..9], scalar => 'Hello World', hash => {qw(foo bar baz qux)}, ); sub foo { my %args = @_; print 's: ', $args{scalar},"\n", 'a: ', join(',', @{$args{array}}),"\n", "h:\n", map "\t$_ => $args{hash}{$_}\n", keys %{$args{hash}}, ; }
    Now you don't have to worry about what order you list the arguments. :)

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
Re: Passing multiple data types to a subroutine
by rbi (Monk) on Nov 28, 2003 at 19:49 UTC
    You can get back the scalars like this:
    my $scalar1 = 1; my $scalar2 = 2; ($scalar1,$scalar2) = my_subroutine(@array1,$scalar1,$scalar2); print "$scalar1 $scalar2 \n"; sub my_subroutine() { @array1 = @{shift()}; $scalar1 = shift(); $scalar2 = shift(); $scalar1 =+ 22; $scalar2 =+ 33; return ($scalar1,$scalar2); }
    Or different types like:
    my @array1 = (1,2); $arrayref = \array1; ($arrayref,$scalar1,$scalar2,$hashref) = my_sub(@array1,$scalar1,$scal +ar2); @array1 = @{$arrayref}; my %hash = %{$hashref}; print "$array[0]\n"; print "$hash{name}\n"; sub my_sub() { @array = @{shift()}; $s1 = shift(); $s2 = shift(); my %hash = {'name'=>'a', 'city'=>'b'}; $array[0] =3; $hash{name} = 'aaa'; return (\@array1,$scalar1,$scalar2,\%hash); }
    Hope this helps.

      Err -- whoa there. There are a few problems with this code. First off, let me clean it up a little (such as actually passing it an array with some elements in it) and feed it though with -w -Mstrict

      my @array = (42,43,44,45); my $scalar1 = 1; my $scalar2 = 2; ($scalar1,$scalar2) = my_subroutine(@array,$scalar1, $scalar2); print "$scalar1 $scalar2\n"; sub my_subroutine() { my @a = @{shift()}; my $s1 = shift(); my $s2 = shift(); print "array: @a\ns1: $s1\ns2: $s2\n"; $s1 += 22; $s2 += 33; return ($s1, $s2); }

      ..to which perl says:

      main::my_subroutine() called too early to check prototype at - line 5. Can't use string ("42") as an ARRAY ref while "strict refs" in use at +- line 9.

      ..which shows the two most glaring bugs in the code. First off, you've given your subroutine a prototype, which only works if your calls to the subroutine are after its declaration. If you move the subroutine to above the call, however, we discover that you're giving the wrong prototype, anyways! (Too many arguments for main::my_subroutine at - line 15, near "$scalar2)")

      You're also passing in an array, and trying to treat it as an array reference in the code. That's what the second error message is telling you.

      These are all vaguely fixable by changing your code to:

      sub my_subroutine(\@$$) { my @a = @{shift()}; my $s1 = shift(); my $s2 = shift(); print "array: @a\ns1: $s1\ns2: $s2\n"; $s1 += 22; $s2 += 33; return ($s1, $s2); } my @array = (42,43,44,45); my $scalar1 = 1; my $scalar2 = 2; ($scalar1,$scalar2) = my_subroutine(@array,$scalar1, $scalar2); print "$scalar1 $scalar2\n";

      ..but don't do that, as prototypes are mostly broken and confusing. This public service announcement has been brought yo you by the letter P and the number 42.

      Networking -- only one letter away from not working
Re: Passing multiple data types to a subroutine
by Hagbone (Monk) on Nov 29, 2003 at 16:08 UTC
    Using references seems like it'll be the way for me to go ... sometimes, even when you know *where* to look, it doesn't help ... unless you know *what* to look for ;).

    Thanks for all the input, suggestions, and prompts on what and where to look

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (2)
As of 2024-04-20 04:01 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found