Algol 68 flexible rows
last modified November 13, 2026
A row declared with fixed bounds keeps those bounds for its whole life. A
flexible row is a row variable whose bounds are part of its value
rather than its mode, so an assignment may give it a row of a different
length. This article describes the FLEX declaration and the
operations that grow, shrink, and empty such a row.
Rows with changeable bounds
FLEX is written before the brackets of a row mode. The
declaration still needs a set of bounds, which say how long the row is before
the program assigns a value to it. The common starting point is an empty row
with bounds 1 and 0.
BEGIN FLEX [1:0] INT row; print((whole(LWB row, 0), " ", whole(UPB row, 0), newline)); row := (1, 2, 3); print((whole(LWB row, 0), " ", whole(UPB row, 0), newline)); row := (7, 8); print((whole(UPB row, 0), newline)) END
The first line reports the empty bounds. The assignment of a three-element
denotation changes the bounds to 1 and 3, and the next assignment shrinks the
row to two elements. The identity declaration with = would have
fixed the length, which is why the variable is declared with no initial
value at all.
$ a68g basic.a68 1 0 1 3 2
Appending to a row
A flexible row does not grow by itself when an element is added, because the language has no operator for joining two integer rows. The operation is written as a procedure that allocates a row one element longer, copies the old elements, and assigns the new row back.
PROC append = (REF FLEX []INT row, INT value) VOID:
( FLEX [1:UPB row + 1] INT wider;
IF UPB row > 0 THEN wider[1:UPB row] := row FI;
wider[UPB wider] := value;
row := wider );
BEGIN
FLEX [1:0] INT row;
FOR i TO 4 DO
append(row, i * i)
OD;
FOR i TO UPB row DO
print((whole(row[i], 0), " "))
OD;
print((newline))
END
The parameter is a REF FLEX []INT, so the procedure can replace
the caller's row. The local wider is one element longer than the
old row, the copy fills the first elements, and the last one receives the new
value. The IF copies the old elements when the row has any, and
for the first call there is nothing to copy. The four calls produce the
squares 1, 4, 9, and 16.
$ a68g append.a68 1 4 9 16
Shrinking and trimming
Assigning a slice to the same flexible row shortens it. The slice is evaluated first and copied into a new row, so the assignment is safe even though it reads and writes the same variable.
BEGIN FLEX [1:5] INT row := (10, 20, 30, 40, 50); row := row[2:4]; print((whole(LWB row, 0), " ", whole(UPB row, 0), " ", whole(row[1], 0), newline)); row := row[1:0]; print((whole(UPB row, 0), newline)) END
The slice keeps three values, and the assignment renumbers them from 1, so
row[1] becomes 20 and the upper bound becomes 3. The empty slice
row[1:0] then reduces the row to bounds 1 and 0, which is the
empty row of the previous section. A row declared without FLEX
rejects both assignments with a message about different bounds.
$ a68g shrink.a68 1 3 20 0
Empty rows and loops
An empty flexible row behaves like any other row with bounds 1 and 0. A loop
that counts from LWB to UPB runs zero times, which
is why the same code handles a row with and without elements.
BEGIN
FLEX [1:0] INT row;
FOR i TO UPB row DO
print((whole(row[i], 0)))
OD;
print(("empty row skipped", newline))
END
The upper bound is 0, so the loop body never runs and only the message is printed. This is the reason a program can append in a loop without testing the length first, and the reason a loop over a row does not need a separate case for the empty one.
$ a68g loop.a68 empty row skipped
Flexible rows as parameters
A parameter of mode []INT accepts a flexible row as well as a
fixed one, and it sees the current bounds. The parameter is a value, so the
procedure may walk the row but cannot resize the caller's variable unless the
parameter is declared with REF.
PROC show = ([]INT row) VOID:
( FOR i FROM LWB row TO UPB row DO
print((whole(row[i], 0), " "))
OD;
print((newline)) );
BEGIN
FLEX [1:0] INT row;
row := (1, 2, 3);
show(row);
row := (9);
show(row)
END
The same procedure prints three values for the first call and one for the
second, because it reads the bounds from its argument. The append procedure
earlier needed REF precisely because it assigns a new row to the
variable rather than only reading it.
$ a68g param.a68 1 2 3 9
Flexible rows of strings
FLEX may be applied to any row mode, and the append procedure
generalises to rows of strings once the element mode is changed. The
procedure then builds a list of names without knowing its final length.
PROC append = (REF FLEX []STRING row, STRING value) VOID:
( FLEX [1:UPB row + 1] STRING wider;
IF UPB row > 0 THEN wider[1:UPB row] := row FI;
wider[UPB wider] := value;
row := wider );
BEGIN
FLEX [1:0] STRING names;
append(names, "Alice");
append(names, "Bob");
FOR i TO UPB names DO
print((whole(i, 0), ": ", names[i], newline))
OD
END
The body is identical to the integer version apart from the modes, which
shows how orthogonal the row concept is. A STRING is itself a
flexible row of characters, so the element mode of this row is flexible as
well. Strings also support +:= directly, which is why appending
to a string needs no procedure of its own.
$ a68g strings.a68 1: Alice 2: Bob
Next steps
The rows article describes fixed rows and
multidimensional rows, and strings shows the
same operations on rows of characters. The
procedures article explains the
REF parameters used by the append procedure, and
loops covers the clauses that walk a row.
Source
Revised Report on the Algorithmic Language Algol 68
In this article we have declared flexible rows, changed their length by assignment, appended elements with a procedure, trimmed a row down to an empty one, walked a row whose bounds can be zero, passed flexible rows to procedures, and built a flexible row of strings. The rows article continues with fixed rows, and strings describes the flexible row of characters.
Author
List all Algol 68 tutorials.