Nested aggregates
=================

Enumerated sets and sequences can be arbitrarily nested (that is, one may create
sets of sets, as well as sequences of sets etc.); tuples can also be nested and 
may be freely mixed with sets and sequences (as long as the proper Cartesian
product parent can be created). Lists can be nested, and one may create lists of 
sets or sequences or tuples.

Multi-indexing
---------------

Since sequences (and lists) can be nested, assignment functions and mutation
operators allow you to use *multi-indexing*, that is, one can use a multi-index 
:math:`i_1, i_2, \ldots, i_r` rather than a single :math:`i` to reach 
:math:`r` levels deep. Thus, for example, if :math:`S=[\ [1, 2],\ [2, 3]\ ]`,
instead of

.. code-block:: magma

   S[2][2] := 4;


one may use the multi-index :math:`2, 2` to obtain the same effect
of changing the 3 into a 4:

.. code-block:: magma

   S[2,2] := 4;

All :math:`i_j` in the multi-index :math:`i_1, i_2, \ldots, i_r` have to be greater
than :math:`0`, and an error will also be flagged if any :math:`i_j` indexes
beyond the length at level :math:`j`, that is, if :math:`i_j>\#S[i_1, \ldots, i_{j-1}]`,
(which means :math:`i_1>\#S` for :math:`j=1`). There is one exception: the last index
:math:`i_r` is allowed to index beyond the current length of the sequence
at level :math:`r` if the multi-index is used on the left-hand side of an
assignment, in which case any intermediate terms will be undefined.
This generalizes the possibility to assign beyond the length of a
"flat" sequence.
In the above example the following assignments are allowed:

.. code-block:: magma

   S[2,5] := 7;

(and the result will be :math:`S=[ [1, 2], [2, 3, undef, undef, 7] ]`)

.. code-block:: magma
    
   S[4] := [7];

(and the result will be :math:`S=[ [1, 2], [2, 3], undef, [7] ]`).
But the following results in an error:

.. code-block:: magma

   S[4,1] := 7;

Finally we point out that multi-indexing should not be confused with
the use of sequences as indexes to create subsequences. For example,
to create a subsequence of :math:`S = [5, 13, 17, 29]` consisting of the
second and third terms, one may use

.. code-block:: magma

   S := [ 5, 13, 17, 29 ];
   T := S[ [2, 3] ];

To obtain the second term of this subsequence one could have done:

.. code-block:: magma

   x := S[ [2, 3] ][2];

(so :math:`x` now has the value :math:`S[3]=17`), but it would have been more efficient
to index the indexing sequence, since it is rather expensive to build
the subsequence :math:`[\ S[2], S[3]\ ]` first, so:

.. code-block:: magma

   x :=  S[ [2, 3][2] ];

has the same effect but is better (of course ``x := S[3]`` would be
even better in this simple example.) To add to the confusion, it is
possible to mix the above constructions for indexing, since one can
use lists of sequences and indices for indexing; continuing our example,
there is now a third way to do the same as above, using an indexing list
that first takes out the subsequence consisting of the second and third
terms and then extracts the second term of that:

.. code-block:: magma

   x :=  S[ [2, 3], 2 ];

Similarly, the construction

.. code-block:: magma

   X :=  S[ [2, 3], [2] ];


pulls out the subsequence consisting of the second term of the
subsequence of terms two and three of :math:`S`, in other words, this
assigns the *sequence* consisting of the element :math:`17`, not
just the element itself!
