Chris Cigno recently wondered why the following subroutine kept printing ’1′:
#-------------------
sub readarg {
#-------------------
my $type = @_;
print $type;
}
Jay Baldwin came back with the correct fix:
my ($type) = @_;I did not want to bother most of the readers of the list with more details on why the second form works correctly and why the first one fails. But this blog is the perfect place to do so.
What is returned by an array (the @-thing) depends on the context.
Every Perl expression is evaluated in one of two `contexts’, either list context or scalar context, depending on whether it is expected to produce a list or a scalar. Many expressions have quite different behaviors in list context than they do in scalar context.
Suppose you have a Perl EXPRESSION, you can put that EXPRESSION in list context:
@array = EXPRESSION;or you can put it in scalar context, by saying:
$scalar = EXPRESSION;One of the expressions that behaves differently depending on the context is the array:
@array1When put in list context it will simply produce a list of the items that are on the array @array1.
@array2 = @array1;The result will be that @array2 will be set equal to the list of items, so it will become a copy of @array1.
But when you evaluate things in scalar context, the @array1 expression will return the number of items on the array @array1.
$var = @array1;Which means that in this case $var will be set equal to the number of items on the array @array1.
Lets take a look at some more examples:
my @array1 = ('alpha', 'beta', 'gamma');
my @array2 = @array1;
my ($first, $second, $third) = @array2Well if you see it written out like that it becomes simple, doesn’t it?
The first line defines @array1 to contain 3 items (lists are always written as things within parenthesis and seperated by comma’s).
Line 2 copies the complete @array1 into array @array2 (remember list context).
And the last line evaluates @array2 again in list context, so it returns a list of 3 items. And this list goes nicely into the new list that we have on the left side of the expression ($first, $second, $third).
The result will be that $first is set to ‘alpha’, $second to ‘beta’ and $third to ‘gamma’.
So what will happen when we change the last line into:
my ($first, $second) = @array2;Well @array2 still returns a list with 3 items, but on the left side we have only 2 items waiting. Nothing bad happens. $first will still contain ‘alpha’ and $second will contain ‘beta’. And one could say that ‘gamma’ goes to waste as it does not ‘fit’ in the list on the left side of the expression.
And what happens if we have too many elements on the left side?
my ($first, $second, $third, $four) = @array2;In this case the 3 elements returned by @array2 in list context will set $first, $second and $third. $fourth will not get set as the is no 4th element returned and will be undefined.
Now the @_ that Chris was using is just an array like any other array, just that it contains the list of arguments that get passed to the subroutine. Chris called the subroutine in the macro or tag expansion as follows:
<mymacro;12p36;56p898;122p087;39p079>
expansion:
</Pb;;myperl.pl;XPP::readarg(q/$*/)>
So actually Perl saw this call as:
readarg('12p36;56p898;122p087;39p079');The list of arguments to the subroutine readarg contains only 1 item, a string containing ’12p36;56p898;122p087;39p079′.
And subsequently when you do:
my $type = @_;you evalute the array @_ in scalar context, so it returns you the number of items, which would be in this case always 1.
But when you do the correct thing:
my ($type) = @_;the one item returned by the list of arguments @_ will set the $type variable, because the parenthesis around $type define a list with 1 item and forced @_ to be evaluated in list context. (just like in the ($first, $second, $third) example).
Perl would not be Perl if there would not be another way to set $type:
my $type = shift @_;
8 users commented on " So why does my $type = @_; not work? "
Follow-up comment rss or Leave a TrackbackYou are so right but sometimes TIMTOWTDI will lead you astray if you are not careful. But I still like Perl because there is always another way.
The thing I keep having to look up over and over is the distinction between arrays and lists (sometimes called “scalar lists”). But I just did (again), and now I get it (again). An array is a nice programming structure, you can push/pop/shift/unshift and address in various ways. An array is initialized by a list. Whereas, a list is just a list; a fixed list of values. You cannot do any of the array functions to it. Lists cannot be changed.
More confusing to me is when you use () vs. [] to enclose the list (although I seem to know when to do it one way vs. the other way, but I do not always understand why).
my @array = (“one”, “two”, “three”);
my %hash =
(
key1 => ["one", "two", "three"],
key2 => ["four", "five"],
key3 => “foobar”
);
which you would address as arrays like so:
my ($one, $two, $three) = @{ $hash{‘key1′} };
(Right, Bart?)
It also seems to be necessary to use the square brackets in subroutine calls, i.e. when the subroutine is expecting a list in order to initialize an array in the sub:
&mysub($arg1, [@{ $hash{'key1'} }], $arg3);
###
sub mysub {
my ($a1,$a2,$a3) = @_;
print “$a1″;
print( join(‘|’, @$a2), “\n” );
print “$a3″;
return;
}
Oh, stupid “smart quotes”. Sorry about that…
Jay, the real answer to your question will require a seperate post. The short answer is that square brackets are used when you want to create a reference to an anomynous array. Your impression that they are used inside subroutines stems from the fact that the arguments of a subroutine are passed as one array (@_), so when you pass two seperate arrays as arguments to a subroutine there is no way you can seperate them out on the receiving end.
Personally I use references to structures (be it array or hashes or a mixture) a lot. Once you get the hang of the syntax it works magically well. But as said all of this is food for a series of upcoming blog entries.
Oh and since I launched the idea of a series of Perl related blog entries, if there are other people out there that have problems with certain areas of the perl syntax, please step forward.
Oh Jay and I forgot to answer the real question in your comment: (Right Bart?)
The line
my ($one, $two, $three) = @{ $hash{’key1′} };
is absolutely correct.
The line:
print( join(’|’, @$a2), “\n” );
might work, but I would always write it as:
print( join( ’|’, @{$a2} ), “\n” );
just to make clear that $a2 is a reference to an array.
To Ray,
Well the advantage of TIMTOWTDI to me is that you can pick the way that suits your personal style best.
I do agree though that not having 1 prescribed way of solving things makes maintenance harder if several people have to work on the same code.
In fact the founding father of Perl (Larry Wall) looks at TIMTOWTDI as one of the fundamentals of the Perl language as it allows people to grow into the language. In the beginning one uses the slow and easy way and over time one learns to use the fast and more difficult ways.
Bart, I am familiar with the use of references to structures… the technique is used a lot in the custom tools the Xy consulting did for us, and I have adopted it, too. For example, I probably wouldn’t really do what I showed above:
&mysub($arg1, [@{ $hash{'key1'} }], $arg3);
I would probably pass a reference to the entire hash into the subroutine instead:
&mysub($arg1, $arg2, \%hash);
and then in the sub get the value out of the hash key:
my ($a1,$a2,$hashref) = @_;
print( join(‘|’, @{$hashref->{‘key1′}}), “\n” );
or reduce the clutter even further by initializing a local array from that hash key:
my ($a1,$a2,$hashref) = @_;
my @a3 = @{$hashref->{‘key1′}};
print( join(‘|’, @a3), “\n” );
Is this what more or less what you were getting at?