We can assign values to the elements of a multiple either individually or collectively.
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:
i7
is 1
is
masked, but the formula LWB i7
ensures that the
correct value is used.
i7
are changed when the program is
being maintained, the loop clause can remain unchanged. This simplifies
the maintenance of Algol 68 programs.
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
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.
x
to y
discussed above,
what is the final value of y
(careful)? AnsREF[,]INT m = LOC[3:5,-2:0]INT, REF[]INT n = LOC[1:3]INT:=(1,2,3)Ans
m[1,]:=n
?
m
to its
third “row”?
Sian Mountbatten 2012-01-19