Intermediate

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.

1
2
3
4
5
6
7
8
# Pull the official image
docker pull codearchaeology/algol68:latest

# Run each example
docker run --rm -v $(pwd):/app -w /app codearchaeology/algol68:latest functions.a68
docker run --rm -v $(pwd):/app -w /app codearchaeology/algol68:latest recursion.a68
docker run --rm -v $(pwd):/app -w /app codearchaeology/algol68:latest scope.a68
docker run --rm -v $(pwd):/app -w /app codearchaeology/algol68:latest higher_order.a68

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 a VOID procedure are both declared with PROC.
  • 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 return keyword; the value of the last expression (often an IF ... 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 factorial and fib read 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 whole for clean numeric output: whole(n, 0) formats an INT as 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
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining