Blog › ICP guides

Beta developer on retainer: inner() extension invocation, pattern inheritance, enter/do/exit blocks, and Beta language programming on monthly retainer

November 12, 2026 · ~15 min read

A Beta program modeling an animal hierarchy was producing four wrong speak calls per run. The program defined an ANIMAL pattern with a speak method whose do-block contained a type-checking guard: if the current animal type matched a special case, the guard executed a LEAVE speak statement that transferred control out of the speak method before reaching the inner call at the end of the block. A DOG subpattern extended ANIMAL with an extend speak do-block that called dog_sound(). In Beta, the inner call is the mechanism by which a superpattern’s method invokes its subpattern’s extension: the execution of DOG’s extend speak do-block is contingent on ANIMAL’s speak do-block reaching and executing the inner statement. When the type guard fired for DOG instances that matched the special case, ANIMAL’s speak method exited via LEAVE speak without reaching inner. DOG’s extension was silently skipped — no error, no warning, no indication that dog_sound() had not run. Four speak calls per run hit this guard path, producing four silent extension failures. The Beta developer on retainer diagnosed the inner() reachability failure: the guard condition was placed after inner() was supposed to be called, inverting the execution order that Beta requires for subpattern extensions to function. The fix restructured ANIMAL’s speak do-block to call inner before the guard check, ensuring that DOG’s dog_sound() always runs before any ANIMAL-level guard logic applies. Wrong speak calls per run: 4 → 0.

The work log entry read “fixed animal speak dispatch, 14h.” It names the symptom and duration. It cannot explain to a client why Beta’s inner() mechanism is the inverse of every other object-oriented language’s method extension model (in Simula, Smalltalk, Java, C++, and most OOP languages, the subclass overrides the superclass method and calls the superclass explicitly via super.speak() or inherited; the subclass is in control of when and whether the superclass code runs; in Beta, the relationship is reversed: the superpattern calls inner from inside its own method body, which is what invokes the subpattern’s extension; the subpattern’s extension is appended to the end of the superpattern’s method — or more precisely, it executes at the point where the superpattern places the inner statement; the subpattern has no mechanism to prevent the superpattern’s code from running, and the superpattern has no mechanism to prevent the subpattern’s extension from running if inner is reached; the only way a subpattern extension is skipped is if the superpattern’s execution path never reaches inner), why this inversion makes guard placement critical in a way it is not in conventional OOP (a guard in a Java method that returns early prevents the subclass override from running only if the override calls super.method() and the guard is in the super method; a guard in a Beta superpattern do-block that exits early with LEAVE prevents the subpattern extension from running because the extension depends on inner() being reached; the programmer must reason about inner() reachability from every execution path of every guard, loop, and conditional in the superpattern do-block), or why the fix required auditing all other superpattern do-blocks in the same program for the same early-exit pattern (any superpattern method that has a conditional exit before inner() is a potential silent subpattern extension bypass; the correctness of the entire extension chain depends on every superpattern in the chain placing inner() at a point that is reachable from every execution path that should invoke the subpattern extension). The 14 hours of inner() reachability analysis, execution path audit across guard conditions, and ANIMAL/DOG co-design review are not visible in the diff beyond reordered statements in the speak do-block.

Beta’s inner() mechanism: extension invocation, the inversion of super calls, and reachability

Beta’s most important and distinctive design decision is the direction of the method extension chain. In conventional OOP, a subclass method overrides the superclass method and calls super.method() from within the override body to incorporate the superclass behavior. The subclass is in the driver’s seat: it controls whether the superclass behavior runs, when it runs relative to the override’s own code, and whether the result is used. In Beta, the roles are inverted: the superpattern method is in the driver’s seat. The superpattern’s do-block contains an inner statement (sometimes written INNER in older Beta literature). When execution reaches inner, control transfers to the most-derived active subpattern’s extension of the current method. The subpattern’s extension do-block runs, then control returns to the superpattern do-block immediately after the inner statement. The subpattern cannot choose not to be invoked when inner() is called; the superpattern cannot skip the subpattern extension except by not reaching inner.

The practical implication: every statement in a superpattern’s do-block that appears before the inner statement runs as setup before the subpattern extension; every statement after inner runs as teardown after the subpattern extension. The superpattern provides a framework (setup → inner → teardown) and the subpattern fills in the extension point (inner). This is structurally the Template Method design pattern, but enforced by the language runtime rather than by a programming convention. A leaf pattern (a pattern with no subpatterns that extend the current method) reaches inner and nothing happens — inner is a no-op at the bottom of the inheritance chain. The chain of inner calls through the hierarchy terminates silently at the leaf. This means superpattern do-blocks never need to check whether they have subpatterns — calling inner is always safe regardless of where in the hierarchy the current instance sits. Retainer work on inner() design involves three recurring tasks: first, placing inner at the correct position in the superpattern do-block relative to setup and teardown code (inner() too early means the subpattern extension runs before setup is complete; inner() too late or unreachable means the extension is silently skipped); second, analyzing every guard, loop, and conditional early-exit path in the superpattern do-block to verify that inner() is reachable from every path that should invoke the subpattern; and third, designing the contract between the superpattern and subpattern extension (what state is guaranteed to be set up before inner() runs, and what cleanup is guaranteed to happen after).

LEAVE and inner() reachability: Beta’s LEAVE patternName statement transfers control out of the named pattern invocation. LEAVE speak inside ANIMAL’s speak do-block exits the current speak invocation immediately, analogous to return in other languages. If inner has not been reached when LEAVE fires, the subpattern extension is never invoked for that invocation. Every conditional guard that could fire LEAVE before inner() is a potential silent extension bypass. The analysis required: for each conditional path in the superpattern do-block that reaches LEAVE, determine whether the intent is to skip the subpattern extension or to invoke it before the early exit. If the intent is “do the extension, then exit early”, inner() must be moved before the LEAVE. If the intent is “skip the extension for this execution path”, the LEAVE before inner() is correct — but this is unusual because it means the superpattern is preventing the subpattern from running, which works against the Beta extension model’s intent of allowing subpatterns to always extend the behavior. The most common correct pattern: inner() early in the do-block (or before any LEAVE), with superpattern teardown or guard logic after inner(); this guarantees the subpattern extension always runs and the superpattern’s post-extension logic may or may not proceed to completion.

Beta patterns: unified type/procedure abstraction, enter/do/exit blocks, and object creation

Beta’s patterns are the language’s central abstraction: a single concept that serves simultaneously as a class (type), a procedure (callable), a coroutine, and an exception. This unification is Beta’s most radical design choice, and it is what distinguishes the language from all other mainstream OOP languages. In Java or C++, a class is a type and a method is a procedure; these are syntactically and semantically distinct constructs with different declaration and invocation syntax. In Beta, both are patterns: a class-like pattern is declared and instantiated; a procedure-like pattern is declared and called; the syntax and execution model are the same. The three-block structure of a Beta pattern invocation — enter, do, exit — unifies the parameter intake, execution body, and return value production of a procedure with the initialization, state-change body, and final-state of an object creation.

The enter block of a Beta pattern is the parameter intake phase: when a pattern is invoked, the enter block receives the arguments. Enter block declarations specify the types and names of input values: a pattern’s enter block might declare (# enter (n: integer, name: string) ... #), accepting an integer and a string as invocation arguments. The enter block runs before the do block, setting up the input state for the pattern execution. The do block is the execution body: it contains the statements that implement the pattern’s behavior, including any inner calls that invoke subpattern extensions. The do block is the analog of a method body in conventional OOP or a procedure body in procedural languages. The exit block is the output production phase: it specifies the values the pattern produces as output after the do block completes. A pattern’s exit block declaring exit result makes result available to the calling context as the pattern’s return value. Patterns used as functions provide a value in their exit block; patterns used as pure procedures have no exit block or have an empty exit block.

Object creation in Beta: to create an instance of a pattern, the pattern is invoked as a constructor using the & syntax. &ANIMAL creates a new ANIMAL instance and initializes it by running ANIMAL’s enter and do blocks with the provided arguments. The resulting object is a Beta value with ANIMAL’s pattern type. Variables in Beta can be declared with a pattern as their type: thisAnimal: @ANIMAL declares a variable holding an ANIMAL value. The @ prefix means “embedded object” (the ANIMAL object is embedded in the enclosing structure, not heap-allocated separately). The ^ prefix means “reference”: thisAnimal: ^ANIMAL declares a reference to an ANIMAL object. The pattern-as-type usage makes Beta’s type declarations and procedure declarations syntactically identical: both use pattern names, and the distinction between using a pattern as a type (declaration) and using it as a procedure (invocation) is determined by context. This unification is what allows Beta to naturally express simulation patterns (coroutines that are also typed objects with state) and exception patterns (control-flow constructs that are also typed objects carrying error information) without separate syntax for these special cases.

Beta pattern inheritance: subpatterns, virtual patterns, origin, and the extension chain

Beta pattern inheritance uses the colon syntax: DOG: ANIMAL declares DOG as a subpattern of ANIMAL. DOG inherits all of ANIMAL’s attributes (nested patterns, variables, and the enter/do/exit blocks) and can extend any of ANIMAL’s methods by declaring extend speak do ... in DOG’s own do-block. The extend keyword marks a method as an extension of the inherited method from the superpattern rather than a new method that hides the inherited one. In an extension do-block, the code in the extension body runs at the point where the superpattern’s do-block reaches inner. Multiple levels of inheritance are supported: if CAT extends DOG and DOG extends ANIMAL, then ANIMAL’s speak do-block calls inner, which runs DOG’s extension, which may itself call inner, which runs CAT’s extension; the inner chain propagates through all levels of the hierarchy from the most-general to the most-specific.

Virtual patterns in Beta: a virtual pattern slot is a pattern attribute declared in a superpattern that subpatterns can fill with a more specific pattern. virtual: speak: (# ... #) declares speak as a virtual pattern in the superpattern, providing a default implementation. A subpattern can fill this slot with a different pattern: speak :< specificSpeak assigns specificSpeak to the speak virtual pattern slot in the subpattern. Virtual pattern fills enable a form of parameterization where the behavior of a superpattern can be customized by replacing one of its internal patterns with a subpattern-provided alternative — analogous to the Strategy design pattern, but at the language level. The distinction between virtual pattern extension (extend speak do ...) and virtual pattern filling (speak :< ...): extension appends behavior to inner(); filling replaces the entire pattern with a different one. Virtual pattern scope in the fill assignment is a common retainer task: the fill assignment speak :< specialSpeak must occur in the correct scope (inside the subpattern’s own body, referencing the subpattern’s copy of the virtual slot) rather than accidentally targeting the superpattern’s original slot.

The origin reference in Beta provides access to the superpattern within the subpattern body, analogous to super in Java or inherited in Delphi. origin.ANIMAL.speak from inside a DOG method explicitly invokes ANIMAL’s speak method rather than going through the polymorphic inner() chain. The origin reference is used when a subpattern needs to explicitly invoke a specific ancestor’s method implementation rather than relying on the inner() propagation. This is uncommon in well-designed Beta hierarchies (the inner() chain is the intended mechanism) but arises in migration scenarios where Beta code interoperates with systems that expect explicit super calls. Pattern nesting: Beta patterns can be nested inside other patterns as attributes. A pattern declared inside another pattern’s body is a nested pattern; instances of the enclosing pattern have their own instances of the nested pattern. Nested patterns can access attributes of their enclosing pattern, creating a natural encapsulation of helper types and procedures that are private to the enclosing pattern. This nesting is more flexible than Java inner classes because in Beta, the nesting is a full part of the pattern’s attribute structure, not a special syntactic form.

Beta’s design legacy: Simula ancestry, Aarhus University, and gbeta

Beta was designed at Aarhus University (Denmark) beginning in the 1980s by Bent Bruun Kristensen, Ole Lehrmann Madsen, Birger Møller-Pedersen, and Kristen Nygaard. Nygaard’s presence as a co-designer gives Beta a unique lineage: Nygaard, along with Ole-Johan Dahl, created Simula at the Norwegian Computing Center in the 1960s — the language that first introduced the concepts of objects, classes, and inheritance that became the foundation of object-oriented programming. Beta was conceived as the direct successor to Simula, carrying Simula’s simulation-centric design philosophy (objects as simulation entities, co-routines as the concurrency model) into a more rigorous and formally designed language framework. The pattern abstraction in Beta is Nygaard’s answer to the question Simula left open: what is the right way to unify classes, procedures, and coroutines into a single principled abstraction? Beta’s answer is the pattern with enter/do/exit blocks and the inner() mechanism.

Beta’s influence on programming language theory is disproportionate to its commercial adoption. The inner() mechanism — the observation that superclass methods should call subclass extensions rather than the reverse — was a deliberate design inversion of Simula’s inner statement (which Simula already had, though it was less prominent than in Beta). This inversion was analyzed and generalized in programming language research through the late 1980s and 1990s: the Beta team’s papers on the pattern abstraction and the enter/do/exit block model were widely cited in OOP research. The Template Method pattern in the Gang of Four Design Patterns book (1994) is essentially the inner() mechanism formalized as a design pattern in languages that lack the linguistic mechanism Beta provides. Languages that later incorporated similar ideas include: Groovy’s category system (which allows extending existing classes with method wrappers in a way that preserves the outer-layer/inner-layer calling order), aspect-oriented programming’s around advice (which wraps method calls with setup/proceed/teardown code similar to Beta’s superpattern do-block calling inner() between setup and teardown statements), and Scala’s stackable trait pattern (which uses abstract override and super.method() to build method chains through a mixin hierarchy, though the direction is reversed from Beta’s inner() model).

gbeta is a generalized Beta designed and implemented by Erik Ernst at Aarhus University, extending Beta with virtual class patterns (patterns that can be specialized by subpatterns of their enclosing pattern — a form of family polymorphism), further qualified virtual patterns, and a more complete formalization of Beta’s type system. gbeta retains full compatibility with Beta programs while adding expressiveness for hierarchies that require coordinated specialization across multiple levels. The MjolnIR compiler (developed at Aarhus) is the primary implementation of gbeta and supports a subset of Beta programs through the gbeta extension. For Beta retainer work, the distinction between standard Beta and gbeta is relevant: gbeta adds qualified virtual patterns (virtual: T: SuperT) and further qualified patterns (further T do ...) that do not exist in standard Beta; code written in gbeta may use these features and requires a developer with gbeta-specific knowledge, not just standard Beta knowledge. The Beta language reference manual (Kristensen, Madsen, Møller-Pedersen, 1993) and the gbeta system report (Ernst, 1999) are the primary specification documents for Beta and gbeta retainer work respectively.

How HourTab tracks Beta developer retainer hours

Beta retainer work shares the invisible-work problem common to all language engineering retainers, with the additional challenge that Beta’s most important retainer tasks — inner() reachability analysis, enter/exit block parameter design, virtual pattern scope verification — require deep understanding of a language model that is the inverse of every other OOP language’s design and that virtually no client has prior exposure to. An inner() reachability fix is a diff with two statements swapped in a do-block; the value is elimination of all silent subpattern extension bypasses from the speak call path, a correct inner() placement contract that guarantees subpattern extensions always run before any guard logic that might exit the superpattern, and a documented understanding of the LEAVE/inner() ordering requirement that prevents the same bypass from being introduced in future guard additions. An enter/exit block parameter audit that adds missing parameters to an enter block is a diff with one additional parameter declaration; the value is correct configuration delivery to the pattern’s execution body for every invocation, elimination of wrong-output-from-missing-input failures, and a parameter contract that correctly names and types all inputs the pattern requires. A virtual pattern scope fix that corrects a fill assignment to target the nested pattern’s slot rather than the outer pattern’s slot is a diff with a scoping prefix change; the value is correct virtual dispatch to the intended specialization for every invocation through the affected pattern hierarchy.

HourTab gives Beta developers a public retainer-hours URL they send to clients — typically programming language research groups using Beta or gbeta for language design experiments, Aarhus University-adjacent teams with Simula/Beta legacy systems in simulation and educational frameworks, and language implementation teams working from Beta’s formal semantics as a design reference — at the start of an engagement. For Beta retainers, each work log entry should name the mechanism (inner() call-site reachability analysis; inner() placement relative to guard conditions; LEAVE/exit bypass path audit; extend do-block correctness relative to superpattern setup; virtual pattern declaration and fill; virtual pattern scope in fill assignment; enter block parameter intake design; do block execution sequencing; exit block output production; origin/SUPER reference design; object creation with & syntax; pattern nesting attribute design; coroutine suspend/resume; simulation activation sequencing; gbeta qualified virtual pattern design; MjolnIR compiler pipeline), the specific pattern name and the inner() bypass path, and the before/after metric. Beta retainers are often compared to Simula developer retainers for simulation and OOP ancestor language work, and to Smalltalk developer retainers for message-passing OOP language engineering. HourTab’s work log makes the inner() reachability analysis, enter/exit parameter design, and virtual pattern scope verification visible to clients who would otherwise see only the symptom — wrong outputs or silently skipped extensions — and not understand why the fix required understanding Beta’s inverted extension model and the enter/do/exit block structure that determines when and how subpatterns are allowed to run.

Track Beta developer retainer hours without the status emails

HourTab gives Beta 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: Beta developer retainers

What does a Beta developer on retainer typically do?

A Beta developer on monthly retainer covers four principal service areas: inner() extension chain design (inner() call-site reachability analysis in all superpattern do-blocks; inner() placement relative to guard conditions; LEAVE/exit structure analysis for inner() bypass paths; subpattern extend do-block correctness given superpattern setup at inner() call time; leaf pattern inner() handling); pattern hierarchy design (superpattern/subpattern inheritance structure; virtual pattern declaration and fill assignment; pattern nesting as attributes; object creation and initialization; origin/SUPER reference design); enter/do/exit block design (enter block parameter intake; do block execution sequencing; exit block output production; pattern-as-type vs pattern-as-procedure invocation); and concurrent/simulation scope if applicable (coroutine suspend/resume; semaphore synchronization; simulation model activation sequencing; gbeta qualified virtual pattern design).

What Beta work is most commonly underlogged in a retainer?

inner() reachability repair (ANIMAL’s speak do-block had LEAVE speak guard before inner(); DOG’s extend speak dog_sound() skipped for guard-triggering instances; 4 wrong calls/run; restructured inner() before guard; wrong calls: 4/run → 0; 14–22 hrs invisible in inner() call-site reachability analysis), enter/exit block parameter mismatch repair (pattern called with more enter arguments than enter block declared; required configuration value missing; 6 wrong outputs/run; restructured enter block to declare all parameters; wrong outputs: 6/run → 0; 10–17 hrs invisible in enter/exit parameter audit), and virtual pattern dispatch repair (fill assignment targeted wrong pattern scope; inner() called default pattern rather than filled subpattern; 5 wrong dispatch calls/run; restructured fill assignment scoping; wrong calls: 5/run → 0; 11–18 hrs invisible in virtual pattern scope analysis).

What are typical Beta developer retainer rates?

Entry-level Beta developers (1–2 years, basic pattern declarations, simple enter/do/exit blocks, fundamental inner() usage) bill at $55–$100/hr. Mid-level Beta engineers (2–4 years, inner() reachability analysis, virtual pattern design, subpattern inheritance chains, pattern nesting, origin/SUPER reference usage) bill at $95–$175/hr. Senior Beta architects (4–8 years, full pattern system design, concurrent coroutine patterns, simulation hierarchies, gbeta generalized extensions, MjolnIR compiler toolchain) bill at $145–$265/hr. Monthly retainer ranges: $1,500–$4,200/mo advisory (15–25 hrs), $6,000–$15,500/mo for full Beta platform engagements.

What should a Beta developer retainer agreement include?

A Beta developer retainer agreement should specify: inner() chain scope (inner() call-site reachability analysis; inner() placement relative to guard conditions; LEAVE/exit bypass path audit; extend do-block correctness; leaf pattern inner() handling); pattern hierarchy scope (superpattern/subpattern inheritance structure; virtual pattern declaration and fill; pattern nesting as attributes; object creation; origin/SUPER reference design); enter/do/exit block scope (enter block parameter intake; do block sequencing; exit block output production; pattern-as-type vs pattern-as-procedure modes); concurrent/simulation scope if applicable (coroutine suspend/resume; semaphore synchronization; discrete event simulation activation; gbeta qualified virtual patterns; MjolnIR compiler); and hour logging format (pattern name; inner() bypass location; guard condition design; before/after metric; Beta vs gbeta; toolchain version).

How should Beta developer retainer hours be logged?

Log each Beta retainer session with: advisory category (inner() call-site reachability analysis; inner() placement relative to guard conditions; LEAVE/exit bypass path; extend do-block correctness; virtual pattern declaration and fill; pattern nesting scope; enter block parameter intake; do block execution sequencing; exit block output production; origin/SUPER reference; object creation with & syntax; coroutine suspend/resume; semaphore synchronization; simulation activation sequencing; gbeta qualified virtual design; MjolnIR compiler), the specific pattern name and inner() bypass bug (ANIMAL’s speak do-block had LEAVE speak before inner(); DOG’s extend speak with dog_sound() skipped for 4 invocations/run; restructured inner() before guard; wrong calls: 4/run → 0), and the before/after observable metric. Include Beta version (standard Beta compiler, gbeta, MjolnIR), whether for simulation or general OOP, and whether the fix required inner() repositioning, guard condition redesign, virtual pattern scope correction, or enter/exit block parameter restructuring.