Functions in ALGOL 68
Learn how to define and call procedures in ALGOL 68 - parameters, return values, recursion, scope, and higher-order procedures with Docker-ready examples
Introduction
In ALGOL 68 there is no separate keyword for “functions” versus “subroutines” — everything is a procedure, declared with the PROC mode. A procedure that yields a value behaves like a mathematical function; a procedure that yields VOID is what other languages might call a subroutine, called purely for its side effects. This uniformity is a direct consequence of ALGOL 68’s famously orthogonal design: a procedure is just another value, with its own mode, that you can store, pass, and return like any integer or string.
As an imperative, procedural language, ALGOL 68 organises programs around these named procedures. What makes it remarkable for 1968 is how modern its procedure facilities feel: parameters are statically typed, recursion is fully supported, and procedures are first-class — they can be passed as arguments, assigned to variables, and returned from other procedures. These ideas, which felt experimental at the time, are now standard in nearly every language you use.
In this tutorial you will learn how to define procedures with parameters and return values, how variable scope works inside and outside procedures, how to write recursive procedures, and how to treat procedures as values you can pass around. All examples run unchanged on Algol 68 Genie (a68g) via Docker.
Defining and Calling Procedures
A procedure declaration names a procedure, lists its parameters with their modes, states the mode of the result, and gives the body after a colon. A value-returning procedure simply ends with an expression whose value becomes the result. A VOID procedure yields nothing and is called for its effect.
Note that we use the standard whole formatting routine to turn an INT into a clean string (whole(n, 0) uses the minimum field width), which keeps the output tidy and predictable.
Create a file named functions.a68:
BEGIN
# square: takes one INT, yields an INT #
PROC square = (INT x) INT: x * x;
# add: two parameters that share the same mode #
PROC add = (INT a, b) INT: a + b;
# greet: a VOID procedure, called only for its side effect #
PROC greet = (STRING name) VOID:
print(("Hello, ", name, "!", new line));
greet("ALGOL 68");
print(("square(7) = ", whole(square(7), 0), new line));
print(("add(10, 5) = ", whole(add(10, 5), 0), new line))
END
Notice that print itself is just a standard procedure. Identifiers may contain spaces (new line, whole), a hallmark of ALGOL 68 that the parser resolves using its mode information.
Recursion
Procedures may call themselves. Because an IF ... THEN ... ELSE ... FI construct is an expression that yields a value, recursive definitions read almost like their mathematical counterparts.
Create a file named recursion.a68:
BEGIN
# Classic recursive factorial #
PROC factorial = (INT n) INT:
IF n <= 1 THEN 1 ELSE n * factorial(n - 1) FI;
# Recursive Fibonacci #
PROC fib = (INT n) INT:
IF n < 2 THEN n ELSE fib(n - 1) + fib(n - 2) FI;
FOR i FROM 1 TO 6
DO
print(("factorial(", whole(i, 0), ") = ", whole(factorial(i), 0), new line))
OD;
print((new line, "First 10 Fibonacci numbers:", new line));
FOR i FROM 0 TO 9
DO
print((whole(fib(i), 0), " "))
OD;
print((new line))
END
Here the same IF ... FI expression that selects the base case also produces the recursive result — there is no separate return statement, the value of the chosen branch is the value of the procedure.
Variable Scope
ALGOL 68 is a block-structured language. A name declared in an outer block is visible to procedures defined within that block, while a name declared inside a procedure’s body is local and disappears when the procedure returns. The := operator assigns to a variable (a REF to a value).
Create a file named scope.a68:
BEGIN
INT counter := 0; # declared in the outer block - visible to procedures below #
# This procedure reaches out and modifies the outer 'counter' #
PROC bump = VOID:
counter := counter + 1;
# This procedure has its own local variable, invisible outside #
PROC local demo = INT:
BEGIN
INT temp := 100; # local to this procedure only #
temp := temp * 2;
temp # the final expression is the result #
END;
bump; bump; bump;
print(("counter after 3 bumps = ", whole(counter, 0), new line));
print(("local demo returns = ", whole(local demo, 0), new line))
END
The variable temp exists only while local demo runs; trying to reference it from the outer block would be a compile-time error. By contrast, counter lives in the enclosing block, so bump can read and update it.
Higher-Order Procedures
Because a procedure is a first-class value, its mode can be written down — PROC (INT) INT is “a procedure taking an INT and yielding an INT”. You can pass such values as arguments and store them in variables, decades before this became common elsewhere.
Create a file named higher_order.a68:
BEGIN
# Takes another procedure as an argument and applies it twice #
PROC apply twice = (PROC (INT) INT f, INT x) INT: f(f(x));
# Two procedures we can pass around - each has mode PROC (INT) INT #
PROC increment = (INT n) INT: n + 1;
PROC double = (INT n) INT: n * 2;
print(("apply twice(increment, 5) = ", whole(apply twice(increment, 5), 0), new line));
print(("apply twice(double, 5) = ", whole(apply twice(double, 5), 0), new line));
# A procedure can also be stored in a variable and reassigned #
PROC (INT) INT op := increment;
print(("op(10) using increment = ", whole(op(10), 0), new line));
op := double;
print(("op(10) using double = ", whole(op(10), 0), new line))
END
The variable op holds a procedure value, so reassigning it changes what op(10) computes. This treatment of procedures as ordinary values is one of ALGOL 68’s most forward-looking ideas.
Running with Docker
We use Algol 68 Genie (a68g) via Docker. Pull the image once, then run each example file.
| |
Expected Output
Running functions.a68:
Hello, ALGOL 68!
square(7) = 49
add(10, 5) = 15
Running recursion.a68:
factorial(1) = 1
factorial(2) = 2
factorial(3) = 6
factorial(4) = 24
factorial(5) = 120
factorial(6) = 720
First 10 Fibonacci numbers:
0 1 1 2 3 5 8 13 21 34
Running scope.a68:
counter after 3 bumps = 3
local demo returns = 200
Running higher_order.a68:
apply twice(increment, 5) = 7
apply twice(double, 5) = 20
op(10) using increment = 11
op(10) using double = 20
Key Concepts
- Everything is a
PROC: ALGOL 68 makes no distinction between functions and subroutines — a value-returning procedure and aVOIDprocedure are both declared withPROC. - Modes describe procedures: A procedure’s type (mode) is fully spelled out, e.g.
PROC (INT) INT, which lets the compiler check calls statically and lets you write procedure-typed parameters and variables. - The body’s final expression is the result: There is no
returnkeyword; the value of the last expression (often anIF ... FI) becomes the procedure’s result. - Multiple parameters of one mode: Parameters sharing a mode can be grouped, as in
(INT a, b), instead of repeating the mode. - Block scope is lexical: Names declared in an enclosing block are visible to inner procedures; names declared inside a procedure are local and vanish on return.
- Recursion is fully supported: A procedure may call itself directly, making definitions like
factorialandfibread almost like their mathematical form. - Procedures are first-class values: They can be passed as arguments, returned, and assigned to variables — a strikingly modern feature for a language designed in 1968.
- Use
wholefor clean numeric output:whole(n, 0)formats anINTas a minimal-width string, avoiding the padded default field width of bare integer printing.
Running Today
All examples can be run using Docker:
docker pull codearchaeology/algol68:latest
Comments
Loading comments...
Leave a Comment