Blog › ICP guides

Modula-2 developer on retainer: DEFINITION MODULE design, EXPORT/IMPORT module boundaries, ADT encapsulation, and Modula-2 type system on monthly retainer

November 7, 2026 · ~17 min read

A Modula-2 program managing a shared counter was producing four wrong counter reads per run. The program had three modules: MODULE A declared a counter variable and exported it via EXPORT counter in its DEFINITION MODULE. MODULE B imported counter via FROM A IMPORT counter and read it to compute totals. MODULE C also imported counter via FROM A IMPORT counter and incremented it inside a PROCEDURE without MODULE B’s knowledge. Both B’s read and C’s write accessed the same exported counter variable through the EXPORT/IMPORT mechanism — the module system made counter globally visible to any module that imported it, while each importing module remained unaware of the other importers. When MODULE C incremented counter mid-sequence and MODULE B subsequently read it, B read the post-increment value rather than the value it expected from before C’s write. Four reads per run hit this interleaved execution window, producing wrong totals each time. The Modula-2 developer on retainer diagnosed the unintended cross-module mutation: Modula-2’s EXPORT mechanism made the counter variable’s storage directly accessible to all importing modules, not just MODULE A, and neither B nor C had a mechanism to coordinate their accesses. The fix restructured MODULE A as an ADT module with a DEFINITION MODULE exporting only set_counter and get_counter procedures, hiding the counter variable in the IMPLEMENTATION MODULE (not in the EXPORT list, therefore invisible to importers). Wrong counter reads per run: 4 → 0.

The work log entry read “restructured counter module, 14h.” It names the symptom and duration. It cannot explain to a client why Modula-2’s EXPORT mechanism makes an exported variable’s storage directly accessible to every importing module (Modula-2 was designed by Niklaus Wirth in 1978 as a successor to Pascal for systems programming on the Lilith workstation; the module system’s EXPORT/IMPORT mechanism was designed to provide controlled namespace visibility — a module can only use names that the exporting module explicitly lists in EXPORT — but an exported variable is accessible as a mutable location by all importing modules simultaneously, without the exporting module being notified of each access), why hiding the variable in the IMPLEMENTATION MODULE and exposing it only through accessor procedures eliminates the cross-module mutation problem (an IMPLEMENTATION MODULE’s variables that are not listed in the corresponding DEFINITION MODULE’s EXPORT clause are private to the IMPLEMENTATION MODULE; no importing module can read or write these variables except through the procedures the DEFINITION MODULE exports; the procedures can serialize access, enforce invariants, or add mutual exclusion around every access path), or why this structural pattern is the Modula-2 ADT idiom that Wirth intended for exactly this use (the DEFINITION MODULE is the specification: it exports the type and its operations; the IMPLEMENTATION MODULE is the body: it holds the representation and implements the operations; code outside the module sees only the specification; the representation is encapsulated; cross-module mutation of representation variables is structurally impossible). The 14 hours of exported-variable audit across all importing modules, ADT procedure interface design, IMPLEMENTATION MODULE restructuring, and verification that no importing module can directly access the counter variable are not visible in the diff beyond changed DEFINITION MODULE and IMPLEMENTATION MODULE files.

Modula-2 module system: DEFINITION MODULE, IMPLEMENTATION MODULE, and EXPORT/IMPORT

Modula-2’s module system is the language’s central contribution to systems programming discipline. Every Modula-2 library unit consists of two files: a DEFINITION MODULE and an IMPLEMENTATION MODULE. The DEFINITION MODULE is the specification: it declares all names that external modules are permitted to use — exported types, procedures, variables, and constants. Everything declared in a DEFINITION MODULE is exported by default; there is no separate EXPORT clause required in the DEFINITION MODULE itself (this differs from the local module syntax where EXPORT QUALIFIED or EXPORT lists appear inside program modules). The IMPLEMENTATION MODULE is the body: it contains the actual variable storage, procedure bodies, and initialization code. Variables declared in the IMPLEMENTATION MODULE that are not declared in the corresponding DEFINITION MODULE are private to the IMPLEMENTATION MODULE and invisible to all importing modules. This two-file split is the structural basis for Modula-2’s information hiding: the DEFINITION MODULE is what you publish; the IMPLEMENTATION MODULE is what you keep.

Import forms in Modula-2: FROM ModuleName IMPORT name1, name2 imports the listed names into the current module’s namespace directly — after this import, name1 can be used without qualification. IMPORT ModuleName imports the module name itself without bringing its exported names into the current namespace — access requires qualification as ModuleName.name1. The qualified form is safer in large programs because it preserves the source-module context at each use site, making cross-module dependencies explicit in the code. The retainer task of FROM...IMPORT chain audit: for each imported name, trace whether it is a variable (potentially mutable by any importer), a procedure (whose VAR parameters may mutate caller variables), a constant (safe), or a type (safe for structural use, potentially unsafe if it is a POINTER type whose allocated nodes can be mutated by any module holding a pointer value). OPAQUE TYPE is Modula-2’s strongest information-hiding mechanism: a type declared as TYPE T; in the DEFINITION MODULE (with no structural definition given) hides its representation entirely; importing modules can declare variables of type T and pass T values to exported procedures, but cannot access any fields or dereference any pointers inside T values because the representation is only visible inside the IMPLEMENTATION MODULE.

The module initialization code in Modula-2: statements written at the end of an IMPLEMENTATION MODULE body, outside any PROCEDURE declaration, constitute the module’s initialization code. This code executes exactly once, when the module is first loaded, before any other module that imports this module executes its own initialization code. Modula-2 implementations guarantee that module initialization runs in import dependency order: if MODULE B imports MODULE A, MODULE A’s initialization runs before MODULE B’s. This ordering guarantee is critical for initialization sequencing in systems where one module’s startup depends on another module’s data structures being initialized. The EXPORT QUALIFIED vs EXPORT distinction in Modula-2 local modules (modules declared inside a procedure or program module, not library modules): a local module with EXPORT QUALIFIED name1 exports name1 only through qualified access as ModuleName.name1; a local module with EXPORT name1 (unqualified export) brings name1 directly into the enclosing scope. Library DEFINITION MODULEs always use qualified access from outside the module; the EXPORT keyword distinction applies only to local module syntax.

The ADT pattern in Modula-2: a DEFINITION MODULE exports a type name (possibly opaque), and a set of procedures that operate on values of that type. The IMPLEMENTATION MODULE declares the type’s full representation as a private RECORD, allocates and initializes it through the exported constructor procedure, and implements all operations. No importing module can read or write the type’s fields directly; all access goes through the exported procedures. This pattern eliminates the exported-variable mutation bug at the structural level: there is no exported variable to mutate. The retainer work of converting an existing exported-variable module to an ADT module requires: auditing the DEFINITION MODULE to identify exported variables; designing a procedure interface (get and set accessors, or higher-level operations that maintain the module invariant); rewriting the DEFINITION MODULE to export only procedures and the type; moving variable declarations into the IMPLEMENTATION MODULE body; updating all importing modules to call the accessor procedures instead of reading or writing the variable directly; and verifying that the EXPORT list of the new DEFINITION MODULE contains no variable names.

Modula-2 procedures: VAR parameters, VALUE parameters, and control flow

Modula-2 procedures are declared with a syntax that makes the parameter passing mode explicit at the declaration site. The full PROCEDURE declaration: PROCEDURE name(params) : ReturnType; declarations; BEGIN statements END name;. The procedure name after END must match the name in the header; this redundancy is intentional and enables parsers and human readers to identify the end of a procedure body at a glance without tracking nesting depth. VALUE parameters (the default): declared without any prefix keyword. When the procedure is called, the argument expression is evaluated and the result is copied into a new local variable. Mutations to a VALUE parameter inside the procedure do not affect the caller’s variable. VALUE parameters for scalar types (CARDINAL, INTEGER, REAL, BOOLEAN, CHAR, enumeration types) are the default and safe choice for input-only parameters. VAR parameters: declared with the VAR keyword before the parameter name in the formal parameter list. A VAR parameter is a reference to the caller’s variable; reading the parameter reads the caller’s variable, and writing the parameter writes to the caller’s variable. VAR parameters are used for output parameters (the procedure computes a value and writes it to the caller’s variable) and for in-place modification (the procedure modifies a large RECORD or ARRAY variable in place, avoiding the cost of copying it into a VALUE parameter).

Function procedures in Modula-2: a PROCEDURE that returns a value has its return type declared after the closing parenthesis of the formal parameter list, preceded by a colon: PROCEDURE max(a, b: CARDINAL) : CARDINAL;. The procedure body uses a RETURN expr statement to return a value; control also returns to the caller when execution reaches the END of the procedure body (which is a return without a value, only valid for non-function procedures). PROCEDURE types: Modula-2 supports first-class procedure values through PROCEDURE type declarations. TYPE BinaryOp = PROCEDURE(CARDINAL, CARDINAL): CARDINAL; declares the type of a procedure that takes two CARDINAL parameters and returns a CARDINAL. A variable of PROCEDURE type can hold any procedure with the matching signature: VAR op: BinaryOp; can be assigned any procedure that takes two CARDINALs and returns a CARDINAL. Procedure variables enable callback patterns and higher-order programming without requiring explicit pointer-to-function syntax as in C.

Control flow constructs in Modula-2: the FOR loop FOR i := start TO end DO ... END iterates with a CARDINAL or INTEGER index from start to end inclusive; the index variable is incremented by 1 each iteration. FOR i := start TO end BY step DO ... END allows a non-unit step value, including negative steps for reverse iteration. The WHILE loop WHILE condition DO ... END tests the condition before each iteration and skips the body if the condition is initially false. The REPEAT loop REPEAT ... UNTIL condition executes the body at least once and tests the condition after each iteration; the loop exits when the condition becomes TRUE. The LOOP/EXIT construct: LOOP ... IF cond THEN EXIT END ... END provides a general-purpose loop with an explicit exit; EXIT transfers control out of the innermost LOOP statement. This pattern is Modula-2’s structured replacement for C’s while(1) { ... break; ... } and supports exit tests at any point in the loop body, not just at the top or bottom. The IF/THEN/ELSIF/ELSE/END multi-branch conditional: Modula-2’s IF statement uses ELSIF (not else if) for additional branches, with a single END terminating the entire IF structure regardless of how many ELSIF branches it has. The CASE statement: CASE expr OF val1: s1 | val2: s2 ELSE sn END dispatches on the value of a CARDINAL, INTEGER, CHAR, or enumeration expression. WITH record opening: WITH record_var DO field_access END opens the record’s field names into the local scope for the duration of the WITH body, eliminating the need to qualify every field access with the record variable name.

Modula-2 type system: CARDINAL, POINTER, RECORD, ARRAY, SET, BITSET

Modula-2’s type system is strongly typed with no implicit type coercions between numeric types. INTEGER and CARDINAL are the two integer types. CARDINAL is unsigned: it holds non-negative integer values from 0 to MAXCARD (implementation-defined, typically 65535 on 16-bit systems or 4294967295 on 32-bit systems). CARDINAL is the intended type for sizes, indices, loop counters, and other quantities that are inherently non-negative. INTEGER covers both positive and negative values. Arithmetic on CARDINAL wraps at MAXCARD without raising an error; the programmer is responsible for avoiding overflow. Mixing CARDINAL and INTEGER in arithmetic expressions requires an explicit type transfer: VAL(INTEGER, cardinalExpr) converts a CARDINAL value to INTEGER, and VAL(CARDINAL, integerExpr) converts an INTEGER to CARDINAL (raising a runtime error if the integer is negative in checked implementations). The VAL, ORD, CHR, FLOAT, and TRUNC transfer functions are Modula-2’s mechanism for explicit type conversion: ORD(enumValue) returns the ordinal position of an enumeration constant; CHR(cardinalValue) returns the character with that ordinal code; FLOAT(cardinalValue) converts an integer to REAL; TRUNC(realValue) converts a REAL to CARDINAL by truncation.

BITSET is a predefined Modula-2 type for sets of bit positions. BITSET values support standard set operators: + for set union, - for set difference, * for set intersection, / for symmetric difference. INCL and EXCL are the procedural mutation operations: INCL(s, i) adds element i to set s (equivalent to s := s + {i}); EXCL(s, i) removes element i from set s. The IN operator tests membership: i IN s returns TRUE if element i is in set s. BITSET is commonly used in systems programming for flag registers, device status words, and bitmask operations. SET OF enumerated type: TYPE Color = (Red, Green, Blue); TYPE ColorSet = SET OF Color; declares a set type whose elements are values of the Color enumeration. Set literals use brace syntax: {Red, Blue} is a ColorSet containing Red and Blue. ARRAY type: TYPE Row = ARRAY [1..N] OF REAL declares a fixed-size array with an explicit index range. The index range is part of the type: ARRAY [1..10] OF REAL and ARRAY [0..9] OF REAL are different types even though both have 10 elements. Multi-dimensional arrays: ARRAY [1..M] OF ARRAY [1..N] OF REAL or equivalently ARRAY [1..M], [1..N] OF REAL. CARDINAL subrange types: TYPE SmallInt = [0..100] declares a subrange of CARDINAL with values from 0 to 100; assignment of a value outside the range raises a runtime error in checked implementations.

POINTER TO type in Modula-2: VAR p: POINTER TO Node declares a pointer variable. NEW(p) allocates a Node record on the heap and assigns its address to p; DISPOSE(p) frees the heap storage pointed to by p and sets p to NIL in some implementations (but not all — the Modula-2 standard does not require DISPOSE to set p to NIL). p^ dereferences the pointer, yielding the Node value; p^.field accesses a field of the record through the pointer. RECORD type: TYPE Node = RECORD key: CARDINAL; next: POINTER TO Node END declares a linked-list node with a key and a next pointer. The recursive type definition (Node contains a POINTER TO Node) is valid in Modula-2 because POINTER TO Node is a fixed-size value (a machine address) regardless of the size of Node. POINTER lifecycle bugs are among the most common Modula-2 retainer tasks: double-DISPOSE (a Node is freed on both the normal exit path and the error exit path because of unstructured control flow; the second DISPOSE on already-freed memory causes undefined behavior); use-after-DISPOSE (a procedure disposes a pointer but the caller still holds a copy of the pointer value and subsequently dereferences it); and DISPOSE omitted on a rare exit path (a Node allocated early in a procedure is freed on the normal path but not on the error path, producing a heap leak that accumulates over repeated error-path traversals).

Modula-2 low-level programming: SYSTEM module, co-routines, and Modula-3

The SYSTEM module is Modula-2’s escape hatch for systems programming work that requires direct hardware access or type-unsafe operations. Importing from SYSTEM: FROM SYSTEM IMPORT WORD, BYTE, ADDRESS, TSIZE, ADR, CAST;. WORD is the machine word type, assignment-compatible with any pointer type and with any type whose SIZE equals SIZE(WORD); this compatibility is the basis for Modula-2’s generic storage allocator design (allocators can work with WORD arrays and accept any word-sized value without knowing its type). BYTE is the byte type, used for I/O buffers and device register access at byte granularity. ADDRESS is the generic pointer type, equivalent to C’s void*; any POINTER type can be assigned to or from an ADDRESS variable. SYSTEM.TSIZE(type) returns the size of the given type in words (not bytes), used for storage allocation calculations. SYSTEM.ADR(variable) returns the ADDRESS of any variable, enabling Modula-2 code to pass variable addresses to external C procedures or to the operating system through system call interfaces. SYSTEM.CAST(type, expr) bit-reinterprets the value of expr as the given type without any conversion; this is the equivalent of C’s unsafe type cast and is used for low-level register manipulation, network packet parsing, and other cases where the programmer knows the bit representation and wants to reinterpret it under a different type.

Co-routines in Modula-2 are created with NEWPROCESS and switched with TRANSFER. NEWPROCESS(proc, addr, size, crt) creates a new co-routine from procedure proc, with its stack allocated at address addr with size bytes of stack space; the co-routine handle is stored in crt (a PROCESS variable). TRANSFER(from, to) suspends the current co-routine (saving its state into from), and resumes the co-routine identified by to. Co-routines in Modula-2 are cooperative: a running co-routine executes until it voluntarily calls TRANSFER; there is no preemptive scheduling. IOTRANSFER(from, to, interrupt) is the interrupt-driven variant: the current co-routine suspends, the to co-routine resumes, and when the specified interrupt fires, IOTRANSFER resumes the from co-routine. This interrupt co-routine mechanism is the basis for device driver design in Modula-2 systems code. PROCEDURE [INTERRUPT] declarations designate interrupt service routines in systems programming environments that support Modula-2 interrupt handling. Inline assembly: SYSTEM.CODE procedures accept machine instruction sequences as arguments, enabling Modula-2 programs to insert architecture-specific instructions without dropping to a separate assembler file.

Modula-3 is the principal successor language to Modula-2, designed at DEC SRC (Digital Equipment Corporation’s Systems Research Center) and Olivetti Research in 1988 by Luca Cardelli, Jim Donahue, Lucille Glassman, Mick Jordan, Bill Kalsow, and Greg Nelson. Modula-3 retains Modula-2’s module system structure but adds garbage collection (automatic memory management; DISPOSE is not needed; the garbage collector traces POINTER TO references and frees unreachable records), exception handling (TRY ... EXCEPT exceptionName => handler ... END and RAISE exceptionName), object types (TYPE T = OBJECT METHODS m(): T END declares an object type with a method), generics (GENERIC INTERFACE and GENERIC MODULE parameterized over types), and an INTERFACE keyword that replaces the DEFINITION MODULE/IMPLEMENTATION MODULE pair. Modula-3’s UNSAFE module designation marks modules that use raw pointers, LOOPHOLE (Modula-3’s equivalent of SYSTEM.CAST), or address arithmetic; the type system otherwise prevents these operations in safe modules. LOOPHOLE: LOOPHOLE(expr, type) reinterprets the bit pattern of expr as the given type, with the same semantics as Modula-2’s SYSTEM.CAST but restricted to UNSAFE modules to make the unsafety explicit. The retainer distinction between Modula-2 and Modula-3 work is significant: Modula-3 engagements frequently involve object type design, exception handling structure, and garbage collector interaction patterns, while Modula-2 engagements focus on DEFINITION/IMPLEMENTATION MODULE boundary design, explicit POINTER lifecycle management, and co-routine concurrency.

Modula-2’s design legacy: structured systems programming and Pascal’s successor

Wirth’s design philosophy for Modula-2 was rooted in a gap he identified in the programming language landscape of the late 1970s. Pascal (which Wirth designed in 1970) was an excellent language for structured programming and algorithm teaching, but it lacked the low-level access primitives needed for operating system development, device driver writing, and direct hardware manipulation. FORTRAN and assembly language provided the low-level access but imposed no structured programming discipline, making large system codebases difficult to reason about and maintain. Modula-2 was designed to occupy the space between Pascal and assembly: structured programming discipline enforced by the type system, combined with the SYSTEM module escape hatch that allowed direct memory access, byte manipulation, and interrupt handling when the application required it. The Lilith workstation project at ETH Zurich (1978–1982), for which Modula-2 was the primary development language, was the proof of concept: an entire workstation operating system, compiler, and application suite written in a single structured language without resorting to assembly except for the lowest-level bootstrap code. Wirth cited Mesa (the systems programming language developed at Xerox PARC for the Alto workstation) as an important contemporary influence alongside his own earlier Modula language (1975), which introduced the module concept that Modula-2 formalized.

Modula-2’s direct influence on Ada is one of the clearest examples of language design cross-pollination in the 1980s. Ada’s package system — package specifications and package bodies, with the specification declaring the public interface and the body implementing it — mirrors Modula-2’s DEFINITION MODULE and IMPLEMENTATION MODULE split almost exactly. Ada’s with clause (bringing a package into scope) mirrors Modula-2’s IMPORT. Ada’s private types (types with a public name but a private representation) correspond to Modula-2’s OPAQUE TYPE. Ada was standardized in 1983, five years after Modula-2’s publication, and the Ada design team explicitly credited Modula-2 as a source for the package system architecture. Modula-2’s module system also became the canonical model for the interface-vs-implementation separation pattern that subsequent languages replicated: Java interfaces (the interface declares the method signatures; the implementing class provides the body), C++ header files (the .h file declares the class interface; the .cpp file implements it), and Swift protocol+extension (the protocol declares requirements; the extension provides a default implementation) all embody the same DEFINITION/IMPLEMENTATION split that Modula-2 formalized.

Modula-2’s descendants form a significant branch of the Wirth language family tree. Oberon (Wirth, 1987) was Wirth’s further simplification of Modula-2, removing features he considered unnecessary complexity (procedure types, variant records, low-priority interrupts) and adding type extensions (the mechanism for object-oriented programming without a class keyword). Oberon-2 (Wirth and Hölzle, 1991) added receiver procedures, which are procedures declared as belonging to a specific type, enabling the object-oriented programming style with dynamic dispatch. Active Oberon added active objects with integrated concurrency. Component Pascal (a descendant developed by Oberon microsystems) retained the Modula/Oberon module discipline while adding component-based software engineering features. The VAR parameter idiom — using VAR parameters as Modula-2’s substitute for C’s pointer-based output parameters — is one of the language design choices that Oberon retained and that distinguishes Wirth-family languages from C-family languages: in C, passing a pointer to a variable is the mechanism for output parameters; in Modula-2 and Oberon, the VAR parameter keyword makes the intent explicit at both the call site and the declaration site, making it clear to the reader that the procedure writes to the caller’s variable.

How HourTab tracks Modula-2 developer retainer hours

Modula-2 retainer work shares the invisible-work problem common to all language engineering retainers, with the additional challenge that Modula-2’s most important retainer tasks — ADT restructuring (DEFINITION MODULE EXPORT list audit across all importing modules; IMPLEMENTATION MODULE private variable identification; accessor procedure interface design; IMPORT chain audit for transitively visible variable mutations) — produce diffs whose surface area is small relative to the cross-module dependency analysis required. Converting an exported counter variable to an ADT module is a diff with a rewritten DEFINITION MODULE (procedures replacing the variable), a rewritten IMPLEMENTATION MODULE (variable moved to the private section, procedures implementing access), and updated call sites in every importing module; the value is elimination of all uncoordinated cross-module mutation of the counter variable, enforcement of the module invariant at every access point, and a module boundary design that structurally prevents future importers from accessing the counter variable directly. A VAR parameter audit across a module’s PROCEDURE declarations that changes a VAR parameter to VALUE is a diff with one keyword removal in one PROCEDURE header; the value is correct call semantics for callers who did not expect their variable to be written by the procedure, elimination of unexpected aliasing between the parameter and the caller’s variable, and a parameter mode that correctly communicates input-only intent to future code readers. A POINTER double-DISPOSE fix is a diff with a restructured control flow and a single DISPOSE guard; the value is elimination of undefined behavior from double-free on all execution paths.

HourTab gives Modula-2 developers a public retainer-hours URL they send to clients — typically embedded systems groups maintaining Modula-2 firmware for industrial controllers and medical devices (Modula-2 was widely used in these domains in the 1980s and 1990s due to its low-level SYSTEM module access combined with structured programming discipline), operating systems research groups with Modula-2-based OS kernels (Lilith, some USCD p-System descendants), and compiler engineering teams studying Wirth-family languages — at the start of an engagement. For Modula-2 retainers, each work log entry should name the mechanism (DEFINITION MODULE EXPORT list design; IMPLEMENTATION MODULE private variable encapsulation; OPAQUE TYPE ADT pattern; FROM...IMPORT chain audit; VAR vs VALUE parameter audit; POINTER TO allocation/DISPOSE lifecycle; RECORD WITH field-access scope; CASE/OF branch design; LOOP/EXIT infinite-loop-with-condition pattern; NEWPROCESS/TRANSFER co-routine design; SYSTEM.ADR address-of and SYSTEM.CAST bit-reinterpretation; FOR/BY loop step design; BITSET INCL/EXCL/IN membership), the specific module and variable name, the cross-module mutation bug, and the before/after metric. Modula-2 retainers are often compared to Pascal developer retainers for Wirth-family language work, to Oberon developer retainers for Wirth-language evolution, and to Ada developer retainers for module-system-derived language engineering. HourTab’s work log makes the cross-module dependency analysis, EXPORT list restructuring, and ADT accessor design visible to clients who would otherwise see only the symptom — wrong counter reads or wrong field values — and not understand why the fix required understanding Modula-2’s EXPORT mechanism and DEFINITION/IMPLEMENTATION MODULE boundary rules.

Track Modula-2 developer retainer hours without the status emails

HourTab gives Modula-2 developers a public URL per client retainer. One link, no login, live burn-down. Your clients stop asking “how many hours do I have left?” and your work log becomes the proof of value that gets the retainer renewed.

See HourTab pricing →

FAQ: Modula-2 developer retainers

What does a Modula-2 developer on retainer typically do?

A Modula-2 developer on monthly retainer covers four principal service areas: DEFINITION MODULE and IMPLEMENTATION MODULE system work (EXPORT list design; OPAQUE TYPE ADT pattern; IMPORT chain audit for transitively visible variable mutations; FROM...IMPORT vs IMPORT ModuleName qualified access design; restructuring exported variables behind accessor procedures); procedure design (VAR vs VALUE parameter mode audit; function procedure return type design; PROCEDURE type variable callbacks for higher-order programming; WITH record field-access scope); type system work (CARDINAL/INTEGER arithmetic with VAL/ORD/CHR/FLOAT/TRUNC transfer functions; POINTER TO lifecycle with NEW/DISPOSE/dereference; RECORD design with field access; ARRAY index range design; BITSET INCL/EXCL/IN operations; SET OF enumerated type; TYPE alias and subrange design); and low-level and co-routine work (SYSTEM module WORD/BYTE/ADDRESS/TSIZE/ADR/CAST; NEWPROCESS/TRANSFER co-routine design; LOOP/EXIT infinite-loop-with-condition restructuring; CASE/OF branch coverage analysis; FOR/BY loop step design).

What Modula-2 work is most commonly underlogged in a retainer?

Exported-variable cross-module mutation repair (exported counter variable in MODULE A’s DEFINITION MODULE imported and mutated by both MODULE B and MODULE C without coordination; 4 wrong counter reads/run; restructured MODULE A as ADT with get_counter/set_counter procedures and private IMPLEMENTATION MODULE variable; wrong reads: 4/run → 0; 14–26 hrs invisible in EXPORT list audit, ADT interface design, and IMPORT chain verification), VAR parameter aliasing repair (VAR parameter passed two POINTER dereferences pointing to overlapping memory; mutations to first parameter corrupted second parameter value; 6 wrong field values/run; restructured to local copies with explicit assignment on exit; wrong values: 6/run → 0; 11–20 hrs invisible in VAR aliasing analysis and parameter mode audit), and POINTER double-DISPOSE repair (allocated Node freed on both normal and error exit paths due to unstructured control flow; second DISPOSE on freed memory caused 3 crashes/run; restructured with single DISPOSE guard on common exit; crashes: 3/run → 0; 9–17 hrs invisible in POINTER lifecycle audit).

What are typical Modula-2 developer retainer rates?

Entry-level Modula-2 developers (1–2 years, DEFINITION/IMPLEMENTATION MODULE basics, simple PROCEDURE declarations, FOR/WHILE loops, basic RECORD and POINTER types) bill at $65–$115/hr. Mid-level Modula-2 engineers (2–4 years, EXPORT/IMPORT module boundary design, OPAQUE TYPE ADT patterns, VAR parameter mode analysis, POINTER lifecycle management, SYSTEM module usage, co-routine design) bill at $110–$195/hr. Senior Modula-2 architects (4–8 years, complete module system architecture, complex cross-module dependency analysis, Modula-2 OS-level systems programming, NEWPROCESS/TRANSFER concurrency design, compiler toolchain work) bill at $160–$285/hr. Monthly retainer ranges: $1,800–$5,000/mo advisory (15–25 hrs), $7,000–$18,000/mo for full Modula-2 platform engagements.

What should a Modula-2 developer retainer agreement include?

A Modula-2 developer retainer agreement should specify: module system scope (DEFINITION MODULE EXPORT list design; IMPLEMENTATION MODULE private variable encapsulation; OPAQUE TYPE ADT pattern; FROM...IMPORT chain audit; qualified vs unqualified import design); procedure scope (VAR vs VALUE parameter mode analysis; function procedure return type design; PROCEDURE type variable callbacks; WITH record field scope); type system scope (CARDINAL/INTEGER arithmetic; POINTER TO lifecycle; RECORD design; ARRAY index range; BITSET and SET OF enumerated type; TYPE alias and subrange; VAL/ORD/CHR/FLOAT/TRUNC transfer functions); low-level scope if applicable (SYSTEM module WORD/BYTE/ADDRESS/TSIZE/ADR/CAST; interrupt PROCEDURE; NEWPROCESS/TRANSFER co-routine; LOOP/EXIT restructuring; CASE/OF branch coverage); and hour logging format (module name; DEFINITION or IMPLEMENTATION; operation type; before/after error metric; implementation — JPI TopSpeed, FST, GNU Modula-2, Ulm’s Modula-2; Modula-2 vs Modula-3 if applicable).

How should Modula-2 developer retainer hours be logged?

Log each Modula-2 retainer session with: advisory category (DEFINITION MODULE EXPORT list design; IMPLEMENTATION MODULE private variable identification; OPAQUE TYPE ADT interface; FROM...IMPORT chain audit; VAR parameter aliasing analysis; VAR vs VALUE parameter mode audit; POINTER TO allocation lifecycle; RECORD WITH field scope; CASE/OF branch coverage; LOOP/EXIT restructuring; NEWPROCESS/TRANSFER co-routine; SYSTEM.ADR/SYSTEM.CAST low-level; BITSET INCL/EXCL/IN; FOR/BY loop step design), the specific module name and cross-module mutation or parameter aliasing bug (DEFINITION MODULE exported counter variable mutated by both MODULE B and MODULE C; 4 wrong counter reads/run; restructured MODULE A as ADT with get_counter/set_counter procedures; wrong reads: 4/run → 0), and the before/after observable metric. Include implementation (JPI TopSpeed Modula-2, FST Modula-2, GNU Modula-2, Ulm’s Modula-2), whether Modula-2 or Modula-3, and whether the fix required EXPORT list restructuring, ADT accessor design, VAR→VALUE parameter mode change, or POINTER lifecycle reorganization.