Why do I get an error when I try to use the reptition assignment operator with an array? - perl

#!/usr/bin/perl
use strict;
use warnings;
my #a = qw/a b c/;
(#a) x= 3;
print join(", ", #a), "\n";
I would expect the code above to print "a, b, c, a, b, c, a, b, c\n", but instead it dies with the message:
Can't modify private array in repeat (x) at z.pl line 7, near "3;"
This seems odd because the X <op>= Y are documented as being equivalent to X = X <op> Y, and the following code works as I expect it to:
#!/usr/bin/perl
use strict;
use warnings;
my #a = qw/a b c/;
(#a) = (#a) x 3;
print join(", ", #a), "\n";
Is this a bug in Perl or am I misunderstanding what should happen here?

My first thought was that it was a misunderstanding of some subtlety on Perl's part, namely that the parens around #a made it parse as an attempt to assign to a list. (The list itself, not normal list assignment.) That conclusion seems to be supported by perldiag:
Can't modify %s in %s
(F) You aren't allowed to assign to the item indicated, or otherwise try to
change it, such as with an auto-increment.
Apparently that's not the case, though. If it were this should have the same error:
($x) x= 3; # ok
More conclusively, this gives the same error:
#a x= 3; # Can't modify private array in repeat...
Ergo, definitely a bug. File it.

My guess is that Perl is not a language with full symbolic transformations. It tries to figure out what you mean. If you "list-ify" #a, by putting it in parens, it sort of loses what you wanted to assign it to.
Notice that this does not do what we want:
my #b = #a x 3; # we'll get scalar( #a ) --> '3' x 3 --> '333'
But, this does:
my #b = ( #a ) x 3;
As does:
( #a ) = ( #a ) x 3;
So it seems that when the expression actally appears on both sides Perl interprets them in different contexts. It knows that we're assigning something, so it tries to find out what we're assigning to.
I'd chalk it up to a bug, from a very seldom used syntax.

The problem is that you're trying to modify #a in place, which Perl evidently doesn't allow you to do. Your second example is doing something subtly different, which is to create a new array that consists of #a repeated three times, then overwriting #a with that value.
Arguably the first form should be transparently translated to the second form, but that isn't what actually happens. You could consider this a bug... file it in the appropriate places and see what happens.

Related

How can I pass a reference of an array defined as constant?

I defined hash and array constants, When passing those to a function, I'll have to pass them as references.
However I'm wondering what the correct syntax is.
Consider this example:
#!/usr/bin/perl
use strict;
use warnings;
use constant AC => qw(a b c);
sub f($)
{
print "ref=", ref $_[0], "\n";
print "$_\n" foreach (#{$_[0]});
}
f(\AC);
When I run it I get:
ref=SCALAR
Use of uninitialized value $_ in concatenation (.) or string at /run/media/whatever/constref.pl line 10.
The Perl debugger prints AC as an array:
13: f(\AC);
DB<1> x AC
0 'a'
1 'b'
2 'c'
DB<2> c
The List Constants section in the constant pragma docs tells us that
Constants may be lists of more (or less) than one value.
...
List constants are lists, not arrays.
This means, among other properties, that one cannot take a reference of that "list constant" as if it were a single entity, like an array variable is; it behaves as a list, a group of scalars.†
In order to accomplish what is asked then we need to build an (anonymous) array reference out of that list and pass that, f([AC])
use warnings;
use strict;
use feature 'say';
use constant AC => qw(a b c);
sub f {
my ($r) = #_;
say "ref=", ref $r;
say for #$r;
}
f( [ AC ] );
This passes the "list constant" as a single value, an array reference, and it prints as expected. However, I don't like having to copy values, nor to further lose any semblance of constant-ness.‡ There are other ways to do this but those are even less palatable to me.§
I'd suggest to reconsider the tool to use when proper read-only variables are needed.
There are other libraries for this and I'd recommend Const::Fast, or Readonly.
use Const::Fast;
const my #const_ary => qw(a b c);
f( \#const_ary ); # same f() from above
use Readonly;
Readonly my #carr => qw(a b c);
f( \#carr ); # same f() from above
These are lexical variables that one can handle like any other. See docs.
† Attempting to formally "take a reference" of a list results in a list of references
\($v, $t) --> \$v, \$t
‡ While the AC itself is a constant, the list that it is associated with isn't read-only
use constant AC => qw(a b c);
(AC)[1] = "other";
say for AC;
prints
a
other
c
They're just not constant.
§ I can see two other ways
The constant pragma produces (is implemented as) a subroutine. Then one could use that and pass it as such, f(\&AC), and then use it as such, say for $r->().
However, now we have to pass and dereference a subroutine off of that list symbol (AC), and get a list. This is a really bad hack.
The code in the question uses a "constant list." One can use a reference instead and that can be passed as such
use constant AC => [ qw(a b c) ];
# same sub f { } as above
f( AC ); # prints as expected
However, I don't see how to dereference AC to get the whole list (#{ AC } doesn't go?), apart from copying it to an arrayref first, like in f(). But then that defies the purpose of having it as a constant -- and all pretense to constant-ness is dropped.

Why is a list of undef not a read-only or constant value in Perl?

Consider the following programs in Perl.
use strict;
use warnings;
my #foo = qw(a b c);
undef = shift #foo;
print scalar #foo;
This will die with an error message:
Modification of a read-only value attempted at ...
Using a constat will give a different error:
1 = shift #foo;
Can't modify constant item in scalar assignment at ...
Execution of ... aborted due to compilation errors.
The same if we do this:
(1) = shift #foo;
All of those make sense to me. But putting undef in a list will work.
(undef) = shift #foo;
Now it prints 2.
Of course this is common practice if you have a bunch of return values and only want specific ones, like here:
my (undef, undef ,$mode, undef ,$uid, $gid, undef ,$size) = stat($filename);
The 9th line of code example in perldoc -f undef shows this, butthere is no explaination.
My question is, how is this handled internally by Perl?
Internally, Perl has different operators for scalar assignment and list assignment, even though both of them are spelled = in the source code. And the list assignment operator has the special case for undef that you're asking about.

How to get sub array?

I have the code below:
#a = ((1,2,3),("test","hello"));
print #a[1]
I was expecting it to print
testhello
But it gives me 2.
Sorry for the newbie question (Perl is a bit unnatural to me) but why does it happen and how can I get the result I want?
The way Perl constructs #a is such that it is equivalent to your writing,
#a = (1,2,3,"test","hello");
And that is why when you ask for the value at index 1 by writing #a[1] (really should be $a[1]), you get 2. To demonstrate this, if you were to do the following,
use strict;
use warnings;
my #a = ((1,2,3), ("test","hello"));
my #b = (1,2,3,"test","hello");
print "#a\n";
print "#b\n";
Both print the same line,
1 2 3 test hello
1 2 3 test hello
What you want is to create anonymous arrays within your array - something like this,
my #c = ([1,2,3], ["test","hello"]);
Then if you write the following,
use Data::Dumper;
print Dumper $c[1];
You will see this printed,
$VAR1 = [
'test',
'hello'
];
Perl lists are one-dimensional only, which means (1,2,(3,4)) is automatically flattened to (1,2,3,4). If you want a multidimensional array, you must use references for any dimension beyond the first (which are stored in scalars).
You can get any anonymous array reference with bracket notation [1,2,3,4] or reference an existing array with a backslash my $ref = \#somearray.
So a construct such as my $aref = [1,2,[3,4]] is an array reference in which the first element of the referenced array is 1, the second element is 2, and the third element is another array reference.
(I find when working with multidimensional arrays, that it's less confusing just to use references even for the first dimension, but my #array = (1,2,[3,4]) is fine too.)
By the way, when you stringify a perl reference, you get some gibberish indicating the type of reference and the memory location, like "ARRAY(0x7f977b02ac58)".
Dereference an array reference to an array with #, or get a specific element of the reference with ->.
Example:
my $ref = ['A','B',['C','D']];
print $ref; # prints ARRAY(0x001)
print join ',', #{$ref}; # prints A,B,ARRAY(0x002)
print join ',', #$ref; # prints A,B,ARRAY(0x002) (shortcut for above)
print $ref->[0]; # prints A
print $ref->[1]; # prints B
print $ref->[2]; # prints ARRAY(0x002)
print $ref->[2]->[0]; # prints C
print $ref->[2][0]; # prints C (shortcut for above)
print $ref->[2][1] # prints D
print join ',', #{$ref->[2]}; # prints C,D
I think you're after an array of arrays. So, you need to create an array of array references by using square brackets, like this:
#a = ([1,2,3],["test","hello"]);
Then you can print the second array as follows:
print #{$a[1]};
Which will give you the output you were expecting: testhello
It's just a matter of wrong syntax:
print $a[1]

Weird behaviour of Perl references

Have a look at this piece of code:
my #arr = (1, 2);
my $ref = \#arr;
my #s = #$ref;
push #s, 4;
print join(", ", #arr) . "\n";
Unexpectedly, the output is "1, 2". What happened? How come I got two different arrays (#s is (1,2,4))?
Of course I get "1, 2, 4" if I write, before the output, something like this:
$ref = \#s;
#arr = #$ref;
But that seems rather clumsy.
I'm used to other OOP languages, in which such a thing wouldn't happen - an object can me modified, regardless of its references.
So can anyone please help me with that?
my #s = #$ref;
makes a copy of the referenced array. After that, #s and #arr are unrelated arrays that just happen to have the same contents (for a while).
If you want to modify the referenced array, you have to use it directly, e.g.:
push #$ref, 4;
There's also the Data::Alias module, which (I think) lets you do what you're asking for. But there's deep magic involved there, and I've never used it myself.

How can I create a multi-dimensional array in perl?

I was creating a multi-dimensional array this way:
#!/usr/bin/perl
use warnings;
use strict;
my #a1 = (1, 2);
my #a2 = (#a1, 3);
But it turns out that I still got a one-dimensional array...
What's the right way in Perl?
You get a one-dimensional array because the array #a1 is expanded inside the parentheses. So, assuming:
my #a1 = (1, 2);
my #a2 = (#a1, 3);
Then your second statement is equivalent to my #a2 = (1,2,3);.
When creating a multi-dimensional array, you have a few choices:
Direct assignment of each value
Dereferencing an existing array
Inserting a reference
The first option is basically $array[0][0] = 1; and is not very exciting.
The second is doing this: my #a2 = (\#a1, 3);. Note that this makes a reference to the namespace for the array #a1, so if you later change #a1, the values inside #a2 will also change. It is not always a recommended option.
A variation of the second option is doing this: my #a2 = ([1,2], 3);. The brackets will create an anonymous array, which has no namespace, only a memory address, and will only exist inside #a2.
The third option, a bit more obscure, is doing this: my $a1 = [1,2]; my #a2 = ($a1, 3);. It will do exactly the same thing as 2, only the array reference is already in a scalar variable, called $a1.
Note the difference between () and [] when assigning to arrays. Brackets [] create an anonymous array, which returns an array reference as a scalar value (for example, that can be held by $a1, or $a2[0]).
Parentheses, on the other hand, do nothing at all really, except change the precedence of operators.
Consider this piece of code:
my #a2 = 1, 2, 3;
print "#a2";
This will print 1. If you use warnings, you will also get a warning such as: Useless use of a constant in void context. Basically, this happens:
my #a2 = 1;
2, 3;
Because commas (,) have a lower precedence than equal sign =. (See "Operator Precedence and Associativity" in perldoc perlop.)
Parentheses simply negate the default precedence of = and ,, and group 1,2,3 together in a list, which is then passed to #a2.
So, in short, brackets, [], have some magic in them: They create anonymous arrays. Parentheses, (), just change precedence, much like in math.
There is much to read in the documentation. Someone here once showed me a very good link for dereferencing, but I don't recall what it was. In perldoc perlreftut you will find a basic tutorial on references. And in perldoc perldsc you will find documentation on data structures (thanks Oesor for reminding me).
I would propose to work through perlreftut, perldsc and perllol, preferably in the same day and preferably using Data::Dumper to print data structures.
The tutorials complement each other and I think they would take better effect together. Visualizing data structures helped me a lot to believe they actually work (seriously) and to see my mistakes.
Arrays contain scalars, so you need to add a reference.
my #a1 = (1,2);
my #a2 = (\#a1, ,3);
You'll want to read http://perldoc.perl.org/perldsc.html.
The most important thing to understand
about all data structures in
Perl--including multidimensional
arrays--is that even though they might
appear otherwise, Perl #ARRAY s and
%HASH es are all internally
one-dimensional. They can hold only
scalar values (meaning a string,
number, or a reference). They cannot
directly contain other arrays or
hashes, but instead contain references
to other arrays or hashes.
Now, because the top level contains only references, if you try to print out your array in with a simple print() function, you'll get something that doesn't look very nice, like this:
#AoA = ( [2, 3], [4, 5, 7], [0] );
print $AoA[1][2];
7
print #AoA;
ARRAY(0x83c38)ARRAY(0x8b194)ARRAY(0x8b1d0)
That's because Perl doesn't (ever) implicitly dereference your variables. If you want to get at the thing a reference is referring to, then you have to do this yourself using either prefix typing indicators, like ${$blah} , #{$blah} , #{$blah[$i]} , or else postfix pointer arrows, like $a->[3] , $h->{fred} , or even $ob->method()->[3]
Source: perldoc
Now coming to your question. Here's your code:
my #a1 = (1,2);
my #a2 = (#a1,3);
Notice that the arrays contain scalar values. So you have to use reference and you can add a reference by using the \ keyword before an array's name which is to be referenced.
Like this:
my #a2 = (\#a1, ,3);
Inner arrays should be scalar references in the outer one:
my #a2 = (\#a1,3); # first element is a reference to a1
print ${$a2[0]}[1]; # print second element of inner array
This is a simple example of a 2D array as ref:
my $AoA = undef;
for(my $i=0; $i<3; $i++) {
for(my $j=0; $j<3; $j++) {
$AoA->[$i]->[$j] = rand(); # Assign some value
}
}