ZetCode

Algol 68 unions

last modified November 13, 2026

A united mode is a mode whose values may belong to one of several other modes. A variable of a united mode holds a value and a mark that says which part of the union it currently belongs to, and a clause reads the value only after naming the variant. This article describes the declaration, the clause that selects a variant, and the parts of the language that work with united values.

Declaring a united mode

UNION takes a list of modes and forms a new mode from them. A value of one of those modes may be assigned to a variable of the union, and CASE … IN … ESAC chooses a branch by the variant that the value currently carries.

basic.a68
MODE NUMBER = UNION(INT, REAL);

BEGIN
  NUMBER n := 42;
  CASE n IN
    (INT i): print(("integer ", whole(i, 0), newline)),
    (REAL r): print(("real ", fixed(r, 0, 2), newline))
  ESAC;
  n := 2.5;
  CASE n IN
    (INT i): print(("integer ", whole(i, 0), newline)),
    (REAL r): print(("real ", fixed(r, 0, 2), newline))
  ESAC
END

The first assignment stores the integer 42 and the case clause takes the INT branch, where the value is available under the new name i. The second assignment stores a real, and the same clause text now takes the other branch. The branch declarator gives the value a name of the branch mode, so the body can use it as an ordinary integer or real.

$ a68g basic.a68
integer 42
real 2.50

Selecting a variant in a procedure

The case clause may be the whole body of a procedure, which keeps the selection in one place. The parameter has the united mode, and the call may pass a value of any of the alternatives.

show.a68
MODE NUMBER = UNION(INT, REAL);

PROC show = (NUMBER n) VOID:
  CASE n IN
    (INT i): print(("int ", whole(i, 0), newline)),
    (REAL r): print(("real ", fixed(r, 0, 2), newline))
  ESAC;

BEGIN
  show(1);
  show(2.75);
  show(3)
END

The calls pass an integer, a real, and another integer, and each argument is united with the parameter mode before the call. The procedure then reports which variant it received. A value of a mode that is not in the union would be rejected at compile time rather than converted.

$ a68g show.a68
int 1
real 2.75
int 3

CASE as an expression

A case clause may yield a value, in which case every branch must yield a value of one common mode. This makes the clause usable inside a formula, which is how a row of mixed numbers is summed.

row.a68
MODE NUMBER = UNION(INT, REAL);

BEGIN
  [1:4] NUMBER values := (1, 2.5, 3, 4.25);
  REAL total := 0;
  FOR i TO 4 DO
    total +:= CASE values[i] IN (INT n): n, (REAL r): r ESAC
  OD;
  print((fixed(total, 0, 2), newline))
END

Each branch yields a real: the integer branch widens its value, and the real branch yields it unchanged. The clause therefore has mode REAL and may be added to the total. The four values sum to 10.75, and the integer elements take part in the sum through the widening in the branch.

$ a68g row.a68
10.75

Unions of structured modes

The alternatives of a union may be structured modes, so one variable can hold a point or a name. A structure display cannot be united directly, because the display has no mode of its own; the value has to be stored in a variable of the structure mode first.

shape.a68
MODE POINT = STRUCT(INT x, INT y);
MODE SHAPE = UNION(POINT, STRING);

PROC describe = (SHAPE s) VOID:
  CASE s IN
    (POINT p): print(("point ", whole(x OF p, 0), " ", whole(y OF p, 0), newline)),
    (STRING name): print(("name ", name, newline))
  ESAC;

BEGIN
  POINT p := (3, 4);
  SHAPE s := p;
  describe(s);
  s := "circle";
  describe(s)
END

The variable starts with the point and later holds a string, and the procedure prints the fields of the point or the text of the string. The declaration SHAPE s := (3, 4) would be rejected, because (3, 4) is a display that needs the mode POINT from its context and a union provides several contexts at once.

$ a68g shape.a68
point 3 4
name circle

A union field in a structure

Unions are often used as the mode of a field, which gives a record one slot that may hold different kinds of value. The selection of the field is followed by the case clause on that field.

entry.a68
MODE NUMBER = UNION(INT, REAL);
MODE ENTRY = STRUCT(STRING name, NUMBER value);

BEGIN
  ENTRY e := ("pi", 3.14);
  CASE value OF e IN
    (INT i): print((name OF e, " integer ", whole(i, 0), newline)),
    (REAL r): print((name OF e, " real ", fixed(r, 0, 2), newline))
  ESAC
END

The entry holds the name pi and a real value, so the REAL branch runs and prints the value with two decimals. The name field is read inside the branch, because the case clause only constrains the union field.

$ a68g entry.a68
pi real 3.14

The OUT branch

When the case clause has an OUT part, that part runs if no branch matches the variant. It is written after the last branch, without a declarator, and closes with ESAC as usual.

out.a68
MODE NUMBER = UNION(INT, REAL);

BEGIN
  NUMBER n := 3.5;
  CASE n IN
    (INT i): print(("int", newline))
  OUT
    print(("other", newline))
  ESAC
END

The clause has a branch for integers only, and the value is a real, so the OUT part prints the fallback line. The compiler accepts the clause even though one alternative of the union has no branch, which makes OUT the place for the cases that the branches do not cover.

$ a68g out.a68
other

When no branch matches

Without OUT, a value whose variant has no branch leaves the case clause with no work to do. In a context that expects a value this is a mistake, but as a statement the clause simply does nothing and the program continues.

nomatch.a68
MODE NUMBER = UNION(INT, REAL);

BEGIN
  NUMBER n := 2.5;
  CASE n IN
    (INT i): print(("int", newline))
  ESAC;
  print(("after", newline))
END

The real value matches no branch, so the clause prints nothing and the next statement prints the message. Leaving out OUT is therefore safe only when every alternative is covered or when doing nothing is intended.

$ a68g nomatch.a68
after

Testing identity with IS

Algol 68 Genie predefines IS and ISNT as tests of reference identity: the result is true when both operands designate the same variable. They do not test which variant a union carries, because that is the work of the case clause.

identity.a68
BEGIN
  INT x := 5;
  REF INT a = x;
  REF INT b = x;
  REF INT c = LOC INT := 5;
  print((a IS b, " ", a IS c, newline));
  print((a ISNT b, " ", a ISNT c, newline))
END

The names a and b both designate x, so a IS b is true, while c designates a separate cell of the same value and the test is false. ISNT is the negation of the same test. Using IS to ask whether a value is an integer, as in n IS INT, is a syntax error in this implementation.

$ a68g identity.a68
T F
F T

Next steps

The structures article describes the modes that most often appear inside a union, and the conditionals article explains the CASE clause on integers that shares its syntax with the case clause on unions. The coercions article covers the uniting coercion that makes show(1) acceptable, and references and the heap explains reference identity in more detail.

Source

Revised Report on the Algorithmic Language Algol 68

The Algol 68 Genie project

In this article we have declared united modes, selected a variant with CASE … IN … ESAC, used the clause in a procedure and as an expression, stored structures and strings in one variable, put a union in a structure field, added an OUT branch, seen what happens when no branch matches, and tested reference identity with IS and ISNT. The references and the heap article continues with the storage that such values live in.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Algol 68 tutorials.