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?
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};
}
| [reply] [d/l] [select] |
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!
| [reply] |
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
| [reply] [d/l] |
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.
| [reply] [d/l] [select] |
Re: Passing multiple data types to a subroutine
by jweed (Chaplain) on Nov 28, 2003 at 19:49 UTC
|
$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. :)
| [reply] [d/l] [select] |
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. | [reply] [d/l] [select] |
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. :)
| [reply] [d/l] [select] |
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.
| [reply] [d/l] [select] |
|
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
| [reply] [d/l] [select] |
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
| [reply] |
|
|