Keywords: pame

Subsections

- Individual assignment
- Collective assignment
- Exercises

# Assigning to multiple names

We can assign values to the elements of a multiple either individually or collectively.

## Individual assignment

You may remember from chapter 3 that we can access an individual element of a multiple by specifying the subscript(s) of that element. For example, suppose that we wish to access the third element of `i7` as declared in the last section. The rules of the language state that a subscripted element of a multiple name is itself a name. In fact, the elaboration of a slice of a multiple name creates a new name. Thus the mode of `i7[3]` is `REF INT`. We can assign a value to `i7[3]` by placing the element on the left-hand side of an assignment:

       i7[3]:=4

Unless you define a new identifier for the new name, it will cease to exist after the above assignment has been elaborated (see below for examples of this).

Since each element of `i7` has an associated name (created by slicing) of mode `REF INT`, it can be used in a formula:

       i7[2]:=3*i7[i7[1]] + ENTIER(4.0/i7[3])

As you can see, an element was used to compute a subscript. It has been presumed that the value obtained after dereferencing lies between `1` and `7` inclusive. If this were not so, a run-time error would be generated. In the above assignment, all three elements on the right-hand side of the assignment would be dereferenced before being used in the formula. Note that subscripting (or slicing or trimming) binds more tightly than any operator. Thus, in the last term in the above example, `i7` would be sliced first, then the yielded name dereferenced, and finally, the new value would be divided into `4.0`.

Here is a `FOR` loop which assigns a value to each element of `i7` individually:

       FOR e FROM LWB i7 TO UPB i7
       DO
          i7[e]:=e**3
       OD

Using the bounds interrogation operators is useful because:

1. The fact that the lower bound of `i7` is `1` is masked, but the formula `LWB i7` ensures that the correct value is used.
2. If the bounds of `i7` are changed when the program is being maintained, the loop clause can remain unchanged. This simplifies the maintenance of Algol 68 programs.
3. The compiler can omit bounds checking. For large multiples, this can speed up processing considerably.

Here is a program which uses a name whose mode is
`REF[]BOOL`. It computes all the prime numbers less than 1000 and is known as Eratosthenes' Sieve:

       PROGRAM sieve CONTEXT VOID
       USE standard
       BEGIN
          INT size = 1000;

          REF[]BOOL flags = LOC[2:size]BOOL;

          FOR i FROM LWB flags TO UPB flags
          DO
             flags[i] := TRUE
          OD;

          FOR i FROM LWB flags TO UPB flags
          DO
             IF flags[i]
             THEN
                FOR k
                FROM 2*i BY --i TO UPB flags
                DO
                   flags[k] := FALSE
                   CO Remove multiples of i CO
                OD
             FI
          OD;

          FOR i FROM LWB flags TO UPB flags
          DO
             IF flags[i] THEN print((i,blank)) FI
          OD
       END
       FINISH

## Collective assignment

There are two ways of assigning values collectively. Firstly, it can be done with a row-display or a `[]CHAR` denotation. For example, using the declaration of `i7` above:

       i7:=(4, -8, 11, ABS "K",
            ABS TRUE, 0, ROUND 3.4)

Notice that the bounds of both `i7` and the row-display are `[1:7]`. In the assignment of a multiple, the bounds of the multiple on the right-hand side must match the bounds of the multiple name on the left-hand side. If they differ, a fault is generated. If the bounds are known at compile-time, the compiler will generate an error message. If the bounds are only known at run-time (see section 5.8 on dynamic names), a run-time error will be generated. The bounds can be changed using a trimmer or the `@` symbol (or `AT`). See chapter 3 for details.

The second way of assigning to the elements of a multiple collectively is to use an identifier of a multiple with the required bounds. For example:

       []INT i3 = (1,2,3);
       REF[]INT k = LOC[1:3]INT := i3

The right-hand side has been assigned to the multiple name `k`.

As mentioned above, parts of a multiple can be assigned using slicing or trimming. For example, given the declarations

       REF[,]REAL x = LOC[1:3,1:3]REAL,
                  y = LOC[0:2,0:2]REAL

and the assignment

       x:=((1,2,3),
           (4,5,6),
           (7,8,9))

we can write

       y[2,0]:=x[3,2]

The multiple name `y` is sliced yielding a name of mode `REF INT`. Then6.5 the multiple name `x` is sliced also yielding a name of mode `REF INT` which is then dereferenced yielding a new instance of the value to which it refers (`8`) which is then assigned to the new name on the LHS of the assignment. Here is an identity-declaration which makes the new name permanent:

       REF INT y20 = y[2,0];  y20:=x[3,2]

which has its uses (see below).

Here are some examples of slicing with (implied) multiple assignments:

       y     := x[@0,@0];
       y[2,] := x[ 1,@0];
       y[,1] := x[ 2,@0]

In the first example, the right-hand side is a slice of a name whose mode is `REF[,]REAL`. Because the slice has no trimmers its mode is also `REF[,]REAL`. Using the `@` symbol, the lower bounds of both dimensions are changed to `0`, ensuring that the bounds of the multiple name thus created match the bounds of the multiple name `y` on the left. After the assignment (and the dereferencing), `y` will refer to a copy of the multiple `x` and the name created by the slicing will no longer exist.

In the second assignment, the multiple `x` has been sliced yielding a name whose mode is `REF[]REAL`. It refers, in fact, to the first “row” of `x`. The `@0` ensures that the lower bound of the second dimension of `x` is `0`. The left-hand side yields a name of mode `REF[]REAL` which refers to the last “row” of the multiple `y`. The name on the right-hand side is dereferenced. After the assignment `y[2,]` will refer to a copy of the first “row” of `x` and the name produced by the slicing will no longer exist.

In the third assignment, the second “row” of `x` is assigned to the second “column” of `y`. Again, the `@0` construction ensures that the lower bound of the second dimension of `x` is zero. After the assignment, the name created by the slicing will no longer exist.

Notice how the two declarations for `x` and `y` have a common formal-declarer on the left-hand side, with a comma between the two declarations. This is a common abbreviation. The comma means that the two declarations are elaborated collaterally (and on a parallel processing computer, possibly in parallel).

It was stated in the section on names that names can be put on the right-hand side of an identity declaration. This is particularly useful for accessing elements of rows. Consider the following:

       REF[]INT r = LOC[100]INT;

       FOR i FROM LWB r TO UPB r DO r[i]:=i*i OD;

       FOR i FROM LWB r TO UPB r-1
       DO
          IF   REF INT ri=r[i], ri1=r[i+1];
               ri > ri1
          THEN ri:=ri1
          ELSE ri1:=ri
          FI
       OD

This is another example of optimisation, but in this case, we need names because the `THEN` and `ELSE` clauses contain assignments. Both `ri` and `ri1` are used thrice in the conditional clause, but the multiple `r` is only subscripted twice in each loop. In the condition following the `IF`, both `ri` and `ri1` would be dereferenced (but not in the identity declarations). The values of `ri` and `ri1` remain constant: the names are assigned new values. You can see from the identity declarations that the modes of the names `ri` and `ri1` are both `REF INT`.

Here is a program fragment which uses a `REF[]REAL` identity declaration for optimisation:

       REF[,]REAL m = LOC[3,4]REAL;   read(m);

       FOR i FROM 1 LWB m TO 1 UPB m
       DO
          REF[]REAL mi = m[i,];
          FOR j FROM LWB mi TO UPB mi
          DO
             REF REAL mij = mi[j];
             mij*:=mij
          OD
       OD;

       print((m,newline))

As you can see, `read` behaves just like `print` in that a whole multiple can be read at one go (see chapter 3 for the use of `print` with multiples). The only difference between the way `read` is used and the way `print` is used is that the values for `read` must be names (or identifiers of names) whereas `print` can use denotations or identifiers of names or identifiers which are not names.

## Exercises

5.7
  After the assignments of `x` to `y` discussed above, what is the final value of `y` (careful)? Ans
5.8
  Given these declarations

         REF[,]INT m = LOC[3:5,-2:0]INT,
         REF[]INT n = LOC[1:3]INT:=(1,2,3)

  Ans
  (a)
    What is wrong with the assignment `m[1,]:=n`?

  (b)
    How would you assign the second “column” of `m` to its third “row”?

5.9
  Modify Eratosthenes' Sieve to compute the 365th prime. Ans