There are many kinds of nested data structures.
The simplest kind to build is a list of lists (also called an array
of arrays, or a multi-dimensional array). It's reasonably easy to
understand, and almost everything that applies here will also be applicable
to the fancier data structures.

The overall list is enclosed by parentheses, not brackets.
That's because you're assigning a list to an array. If you didn't want
the result to be a list, but rather a reference to an array, then you
would use brackets on the outside:

$ref_to_LoL is a reference to an array, whereas
@LoL is an array proper. The parentheses
(indicating a list) have changed to brackets (indicating the creation
of a reference to an array). Unlike C, Perl doesn't allow you to
freely interchange arrays with references to arrays. This is a
feature.

Remember that there is an implied -> between every pair of
adjacent braces or brackets. Therefore these two lines:

$LoL[2][2]
$ref_to_LoL->[2][2]

are equivalent to these two lines:

$LoL[2]->[2]
$ref_to_LoL->[2]->[2]

There is, however, no implied
-> before the first pair of brackets, which is
why the dereference of $ref_to_LoL requires the
->.

It doesn't matter whether the subscripted elements of @LoL are already
there or not; Perl will gladly create them for you, setting
intervening elements to the undefined value as need be. If you just want to append to a row, you have
to do something a bit funnier looking:

In fact, that wouldn't even compile, because the argument to push must be a real array, not just a reference
to an array. Therefore, the first argument absolutely must begin
with an @ character. What comes after the
@ is somewhat negotiable.

Now it's time to print your data structure. If you only want one element, do this:

print $LoL[0][0];

If you want to print the whole thing, though, you can't just say:

print @LoL; # WRONG

because you'll get references listed, and Perl will never
automatically dereference thingies for you. Instead, you have to roll
yourself a loop or two. The following code prints the whole
structure, using the shell-style for
construct to loop through the outer set of subscripts:

for $array_ref ( @LoL ) {
print "\t [ @$array_ref ],\n";
}

Beware of the brackets. In this and the following example, the
(non-subscripting) brackets do not indicate the creation of a
reference. The brackets occur inside a quoted string, not in a place
where a term is expected, and therefore lose their special meaning.
They are just part of the string that print outputs.

If you want to get at a slice (part of a row) in a multi-dimensional
array, you're going to have to do some fancy subscripting. That's
because, while we have a nice synonym for a single element via the
pointer arrow, no such convenience exists for slices.
However, you can always write a loop to do a slice operation.

Here's how to create a one-dimensional slice of one subarray of a
two-dimensional array, using a loop. We'll assume a list-of-lists
variable (rather than a reference to a list of lists):

In this example, the individual values within each subarray of
@newLoL are assigned one by one, taken from the
appropriate locations in @LoL. An alternative is
to create anonymous arrays, each consisting of a desired slice of a
subarray of @LoL, and then put references to these
anonymous arrays into @newLoL. So we are writing
references into @newLoL (subscripted once, so to
speak) instead of subarray values into a twice-subscripted
@newLol. This method eliminates the innermost loop:

As mentioned previously, every array or hash in Perl is implemented in
one dimension. "Multi-dimensional" arrays, too, are one-dimensional, but
the values in this one-dimensional array are references to other data
structures. If you print these values out without
dereferencing them, you will get the references rather than the data
referenced. For example, these two lines:

@LoL = ( [2, 3], [4, 5, 7], [0] );
print "@LoL";

result in:

ARRAY(0x83c38) ARRAY(0x8b194) ARRAY(0x8b1d0)

On the other hand, this line:

print $LoL[1][2];

yields 7 as output.

Perl dereferences your variables only when you employ one of the
dereferencing mechanisms. But remember that
$LoL[1][2] is just a convenient way to write
$LoL[1]->[2], which in turn is a convenient way to write
${$LoL[1]}[2]. Indeed, you could write all your dereferencing
operations with braces, but that would be uglier than ugly.
Use the syntactic sugar Perl provides to sweeten your program.

@LoL was defined as an array whose values happened to be
references. Here's a similar-looking, but very different case:

Here, $listref is not an array, but a scalar
variable referring to an array--in this
case, referring to an anonymous, multi-dimensional array, the one
created by the outer brackets. Therefore, to print
elroy in this example, we should have said:

print $listref->[2][2];

By contrast, $listref[2] in the erroneous print
statement is the second element in a not-yet-declared array. If you
ask to

use strict 'vars'; # or just use strict

then the use of the undeclared array will be flagged as an error at compile time.

In constructing an array of arrays, remember to take a reference for the
daughter arrays. Otherwise, you will just create an array containing the
element counts of the daughter arrays, like this:

for $i (1..10) {
@list = somefunc($i);
$LoL[$i] = @list; # WRONG!
}

Here @list is being accessed in a scalar context, and therefore
yields the a count of its elements, which is assigned to
$LoL[$i]. The proper way to take the reference will be shown
in a moment.

Another common error involves taking a reference to the same memory location
over and over again:

for $i (1..10) {
@list = somefunc($i);
$LoL[$i] = \@list; # WRONG!
}

Every reference generated by the second line of the for loop is
the same, namely, a reference to the single array @list. Yes, this
array is being given a different set of values on each pass through the
loop, but when everything is said and done, $LoL contains a set of
identical references to the same array, which now holds the last set of
values that were assigned to it.

Here's a more successful approach:

for $i (1..10) {
@list = somefunc($i);
$LoL[$i] = [ @list ];
}

The brackets make a reference to a new array with a copy
of what's in @list at the time of the assignment.

A similar result--though much more difficult to read--would be produced by:

for $i (1..10) {
@list = somefunc($i);
@{$LoL[$i]} = @list;
}

Since $LoL[$i] needs to be a reference, the reference springs
into existence. Then, the preceding @ dereferences this new
reference, with the result that the values of @list are assigned
(in list context) to the array referenced by $LoL[$i]. For
clarity's sake, you might wish to avoid this construct.

But there is a situation in which you might use it.
Suppose $LoL is already an array of references to arrays.
That is, suppose you had made assignments like:

$LoL[3] = \@original_list;

And now suppose that you want to change @original_list (that is,
you want to change the fourth row of $LoL) so that it
refers to the elements of @list. This code will work:

@{$LoL[3]} = @list;

In this case, the reference itself does not change, but the elements of
the array being referred to do. You need to be aware, however,
that this approach overwrites the values of @original_list.

Finally, the following dangerous-looking code actually works fine:

for $i (1..10) {
my @list = somefunc($i);
$LoL[$i] = \@list;
}

That's because the my variable is
created afresh each time through the loop. So even though it looks as
though you stored the same variable reference each time, you actually did
not. This is a subtle distinction, but the technique can produce more
efficient code, at the risk of misleading less enlightened programmers.
It's more efficient because there's no copy in the final assignment.
On the other hand, if you have to copy the values anyway (which the
first assignment above is doing), then you might as well use the copy
implied by the brackets and avoid the temporary variable: