Procedures, Routine Texts & Higher-Order Programming
PROC name = routine text is an identity declaration: the identifier is bound to a routine value of mode PROC (REAL) REAL, PROC (STRING) VOID, PROC INT, etc.
31.1 Procedure declarations
PROC name = routine text is an identity declaration: the identifier is bound to a routine value of mode PROC (REAL) REAL, PROC (STRING) VOID, PROC INT, etc.
PROC square = (REAL x) REAL: x * x;
PROC greet = (STRING name) VOID:
print(("Hello, ", name, "!", newline));
PROC factorial = (INT n) INT:
IF n <= 1 THEN 1 ELSE n * factorial(n - 1) FI;
PROC roll die = INT: ENTIER (random * 6) + 1; # no parameters #31.2 Parameters
Parameters behave like identity declarations: the formal is bound to the value of the actual (call by value). For in-out parameters pass a reference:
There is no call by name: to pass an expression to be re-evaluated, pass a procedure (§31.4).
PROC swap = (REF INT a, b) VOID: (INT t = a; a := b; b := t);
PROC divmod = (INT a, b, REF INT q, r) VOID: (q := a OVER b; r := a MOD b);
INT x := 1, y := 2;
swap(x, y); # works for any two INT variables — unlike ALGOL 60 #
INT q, r;
divmod(17, 5, q, r);31.3 Procedure variables
PROC (REAL) REAL f := sin;
print(f(pi / 2));
f := (REAL x) REAL: x * x; # assign a routine text #
print(f(3))31.4 Routine texts as anonymous functions; Jensen's device revisited
The explicit routine text (INT k) REAL: 1 / k replaces ALGOL 60's implicit thunk — clearer and type-checked.
BEGIN
PROC sum = (INT lo, hi, PROC (INT) REAL term) REAL:
BEGIN
REAL s := 0;
FOR k FROM lo TO hi DO s +:= term(k) OD;
s
END;
print(("H(100) = ", fixed(sum(1, 100, (INT k) REAL: 1 / k), 0, 6), newline));
print(("sum of squares = ", fixed(sum(1, 10, (INT k) REAL: k * k), 0, 0), newline))
END31.5 Higher-order procedures
BEGIN
PROC map = (PROC (INT) INT f, [] INT a) [] INT:
BEGIN
[LWB a : UPB a] INT r;
FOR i FROM LWB a TO UPB a DO r[i] := f(a[i]) OD;
r
END;
PROC fold = (PROC (INT, INT) INT f, INT init, [] INT a) INT:
BEGIN
INT acc := init;
FOR i FROM LWB a TO UPB a DO acc := f(acc, a[i]) OD;
acc
END;
PROC filter = (PROC (INT) BOOL keep, [] INT a) [] INT:
BEGIN
FLEX [1:0] INT r;
FOR i FROM LWB a TO UPB a DO
IF keep(a[i]) THEN
[UPB r + 1] INT t; t[1:UPB r] := r; t[UPB t] := a[i]; r := t
FI
OD;
r
END;
[] INT xs = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
print((map((INT x) INT: x * x, xs), newline));
print((fold((INT a, b) INT: a + b, 0, xs), newline));
print((filter((INT x) BOOL: NOT ODD x, xs), newline))
END31.6 Scope: why ALGOL 68 routines are not full closures
A routine text may use identifiers from enclosing ranges, but its scope is limited to the smallest range containing those declarations. Returning such a routine outside that range is a scope violation:
a68g detects scope violations (at compile time where it can, otherwise at run time). This keeps ALGOL 68's stack discipline: no heap-allocated environments are needed. Workarounds:
Pass the extra data explicitly as a parameter.
Use partial parametrisation, an extension proposed for ALGOL 68 and supported by a68g (not part of the Revised Report): supply some arguments and leave others empty.
PROC make adder = (INT n) PROC (INT) INT:
(INT x) INT: x + n; # uses n, a parameter of make adder #
PROC (INT) INT add5 = make adder(5); # ERROR: the routine outlives n #PROC add = (INT n, x) INT: n + x;
PROC (INT) INT add5 = add(5, ); # a68g partial parametrisation #
print(add5(10)) # 15 #31.7 Nested procedures and recursion
Procedures can be declared in any serial clause, nested to any depth, and are recursive by default (an identity declaration's routine body may refer to the identifier being declared). Mutual recursion works when both procedures are declared in the same range.