Functions in Modula-2
Learn procedures and function procedures in Modula-2 - value and VAR parameters, scope, nested procedures, recursion, and procedure types with Docker-ready examples
Modula-2 is an imperative, procedural, and modular language, and the procedure is its fundamental unit of abstraction. Wirth made a deliberate distinction that many later languages blurred: a proper procedure performs an action and returns nothing, while a function procedure computes and returns a value. Both are declared with the same PROCEDURE keyword — the presence of a return type is what separates them.
Because Modula-2 is strongly and statically typed, every parameter and every return value has a declared type that the compiler checks at every call site. The language also gives you precise control over how arguments are passed: value parameters receive a private copy, while VAR parameters are passed by reference so the procedure can modify the caller’s variable. This explicit choice — invisible in many dynamic languages — is central to writing correct Modula-2 code.
In this tutorial you will learn how to declare and call procedures, the difference between value and VAR parameters, how variable scope works (including Modula-2’s distinctive nested procedures), how to write recursive procedures, and how procedures themselves can be stored in variables and passed as arguments through procedure types.
Proper Procedures and Function Procedures
A function procedure declares a return type after the parameter list and uses RETURN to produce its result. A proper procedure has no return type and simply executes statements.
Create a file named functions.mod:
| |
Notice that:
- The procedure name is repeated in the
ENDclause (END Square;) — just like modules, this catches copy-paste errors. Squareis called inside an expression because it yields a value;Greetis called as a statement because it does not.name: ARRAY OF CHARis an open array parameter — it accepts a character array of any length, so the same procedure handles any string literal.
Value Parameters vs VAR Parameters
By default, parameters are passed by value: the procedure receives a copy, and changes to it do not affect the caller. Prefix a parameter with VAR to pass it by reference, allowing the procedure to modify the original variable.
Create a file named parameters.mod:
| |
The Swap procedure is impossible to write with value parameters alone — without VAR, it would only shuffle copies. Because Modula-2 forces you to mark by-reference parameters explicitly, a reader can tell at a glance which arguments a procedure might modify.
Variable Scope and Nested Procedures
Variables declared with VAR at the module level are global to every procedure in the module. Variables declared inside a procedure are local — they exist only while the procedure runs. Modula-2 also allows procedures to be nested inside other procedures, and an inner procedure can see the local variables of the procedure that encloses it.
Create a file named scope.mod:
| |
Inner is invisible outside of Outer — nesting lets you hide helper procedures exactly where they are used, keeping the module’s namespace clean. This is the same information-hiding instinct that drives Modula-2’s module system, applied at the procedure level.
Recursion
A function procedure may call itself. Recursion expresses naturally definitions like factorial and the Fibonacci sequence. Here we use CARDINAL (unsigned integers) since these values are non-negative.
Create a file named recursion.mod:
| |
Each recursive call must move toward the base case (n <= 1 or n < 2); otherwise the recursion never terminates. Modula-2 places no special restriction on recursion — a function procedure can call itself directly, as shown, or indirectly through other procedures.
Procedure Types: Procedures as Values
Modula-2 treats procedures as first-class enough to store in variables and pass as arguments. You declare a procedure type describing the parameter and return types, then any matching procedure can be assigned to a variable of that type. Only procedures declared at the outermost module level (not nested) may be used this way.
Create a file named higherorder.mod:
| |
The Apply procedure does not know in advance which operation it will run — Double, Negate, or any other procedure matching IntFunc. Procedure types give Modula-2 a form of higher-order programming while keeping the compiler’s full type checking: assigning a procedure with the wrong signature to op is a compile-time error.
Running with Docker
Each example is a standalone program module. Pull the image once, then compile and run each file with gm2.
| |
Expected Output
Running functions.mod:
Hello, Modula-2!
5 squared is 25
Running parameters.mod:
After TryToChange, p = 10
After Swap, p = 20, q = 10
Running scope.mod:
counter = 3
Called from Inner
Running recursion.mod:
5! = 120
Fibonacci: 0 1 1 2 3 5 8 13 21 34
Running higherorder.mod:
Apply(Double, 7) = 14
op(7) = -7
Key Concepts
- Two kinds of procedures — a function procedure declares a return type and is used in expressions via
RETURN; a proper procedure has no return type and is called as a statement. Both use thePROCEDUREkeyword. - Value parameters are copies — modifying a value parameter never affects the caller’s variable.
VARparameters pass by reference — prefix a parameter withVARso the procedure can modify the caller’s variable; this is required for operations likeSwap.- Scope is lexical — module-level
VARdeclarations are global to all procedures, while procedure-local variables live only for the duration of a call. - Nested procedures hide helpers — a procedure declared inside another is visible only within its parent and can read the parent’s local variables.
- Recursion is fully supported — a function procedure may call itself, provided each call advances toward a base case.
- Procedure types enable higher-order code — declare a
PROCEDURE (...)type, then store matching top-level procedures in variables or pass them as arguments, all with compile-time type checking. - The
ENDclause repeats the name —END Square;mirrorsEND ModuleName.and helps the compiler catch structural mistakes.
Running Today
All examples can be run using Docker:
docker pull codearchaeology/modula-2:latest
Comments
Loading comments...
Leave a Comment