Blog › ICP guides
Sather developer on retainer: own vs shared attributes, iter protocol, inclusion polymorphism, pSather parallel loops, and Sather OOP engineering on monthly retainer
October 30, 2026 · ~18 min read
A Sather data processing system was producing six state corruption errors per session. The system used a class hierarchy: a base class COUNTER with an integer attribute count, and a subclass RATE_COUNTER that extended COUNTER to track per-interval counts. The RATE_COUNTER implementation called count := count + 1 to increment the counter. After running several RATE_COUNTER instances concurrently, all instances showed the same counter value — incrementing one RATE_COUNTER incremented all of them. The Sather developer on retainer diagnosed the root cause: RATE_COUNTER had no own count: INT declaration in its class body. In Sather, when a subclass does not declare its own attribute by the same name, mutations to that attribute via := update the attribute on the ancestor class that declared it — the COUNTER class. All RATE_COUNTER instances and the COUNTER base shared a single count attribute on the ancestor. The fix added own count: INT := 0 to the RATE_COUNTER body, giving each RATE_COUNTER instance its own per-instance counter slot that shadows the inherited one without touching the ancestor. State corruptions: 6 per session → 0.
The work log entry read “fixed rate counter state corruption bug, 12h.” It names the symptom and the duration. It cannot explain to a client why Sather’s attribute inheritance model requires explicit own redeclaration to create per-instance attributes in subclasses (rather than automatically creating per-instance copies as Java fields do), why the mutation count := count + 1 wrote to the ancestor attribute rather than the subclass attribute (because without a local own count, there is no subclass attribute to write to — the name resolves to the ancestor’s attribute), why shared is a distinct keyword that does create shared class-level attributes intentionally (as opposed to the unintentional sharing caused by the missing own), or how this interacts with RATE_COUNTER’s init routine (which must also set count := 0 to initialize the per-instance attribute after own count: INT := 0 is declared). The 12 hours of class hierarchy audit (tracing which attributes are owned by which class in the hierarchy), attribute dispatching rule analysis (determining how Sather resolves attribute names across inheritance levels), init routine design (ensuring each subclass initializes its own attributes correctly), and multi-instance testing (verifying that each RATE_COUNTER instance has independent counter state) are not visible in the diff beyond the added own count: INT := 0 declaration.
Sather’s attribute system: own, shared, :=, and the init routine
Sather is an object-oriented language designed at UC Berkeley in the 1990s by Stefan Omohundro and colleagues, inspired by Eiffel but targeting C-level performance and a cleaner semantic model. Sather distinguishes three types of class members: own attributes (per-instance state), shared attributes (class-level static state shared across all instances), and routines (methods). An own attribute declared as own count: INT in a class body means that each instance of that class has its own separate count variable. A shared attribute declared as shared total: INT means that all instances of the class share a single total variable at the class level. The own and shared keywords are explicit: Sather requires the programmer to state which type of attribute is intended, rather than defaulting to per-instance as Java does.
The critical inheritance rule: when a subclass inherits an attribute from a superclass and does not redeclare it with own, the subclass does not create new per-instance storage for that attribute — it uses the superclass’s attribute directly. := in Sather performs in-place mutation: count := count + 1 resolves the name count through the class hierarchy and mutates the attribute wherever it is stored. If count is stored in COUNTER (the ancestor), the mutation happens there. The only way to give a subclass its own independent copy is to explicitly declare own count: INT in the subclass body, which creates new per-instance storage that shadows the inherited name. This is the correct pattern for subclass-specific state that must be independent across instances. The common mistake — omitting the own redeclaration while writing mutation code that assumes per-instance isolation — is the most frequently diagnosed bug in Sather class hierarchy retainer audits.
Sather’s init routine is the equivalent of a constructor. When a new instance of a class is created with create, Sather calls the init routine if one is defined. The init routine is responsible for initializing all per-instance own attributes to correct starting values. For a subclass that adds new own attributes, the init routine must initialize those attributes. Sather allows init to call precall init to invoke the superclass init first, enabling correct initialization of inherited attributes followed by subclass-specific initialization. The retainer work pattern after adding own count: INT := 0 to a subclass always includes verifying that the subclass’s init routine (or the superclass chain’s init) correctly initializes the new attribute. The := 0 in the attribute declaration is a default initializer but does not replace an explicit init routine for more complex initialization logic.
Sather’s mutation model: := is the in-place mutation operator for all types. Sather also supports functional-style update patterns through routines named set_foo(value) that create updated copies — preferred in contexts where immutability is important. The res keyword designates the return value of a Sather routine: inside a routine body, res := value sets the return value, and the routine returns res at the end. This is different from many languages where return value immediately exits the routine; in Sather, res := value sets the return accumulator and the routine continues unless explicitly exited. The self variable inside a routine refers to the current object (the receiver of the routine call), equivalent to this in Java. Type coercions use explicit conversion routines: inti(x) converts a real to an integer, dble(x) converts an integer to a double, flt(x) converts to a float. Sather does not perform implicit numeric coercions.
Sather’s iteration protocol: iter, yield, break!, at_end!, and loop
Sather’s iteration protocol is built around iter routines — special routines whose names end with ! (the exclamation mark is a naming convention signaling iterator semantics) and that use yield to produce successive values. An iter routine is declared like a regular routine but with a ! suffix: elements!: ELT declares an iterator that yields elements of type ELT. Inside the body, yield elem produces the next value and suspends the iterator. The caller’s loop construct consumes the iterator: loop elem := list.elements!; process(elem) end. The loop statement executes the body repeatedly, calling elements! on each iteration; when the iter routine returns without yielding (termination), the loop exits. The exclamation-mark naming convention makes iterator calls immediately recognizable in code: any call ending in ! is an iterator call that fits inside a loop.
break! is a built-in sentinel iterator that terminates the innermost loop immediately when called. It is used inside a loop body to exit early based on a condition: loop elem := list.elements!; if elem = target then break! end end. The break! call terminates the loop as if the outermost iterator had returned. This is the Sather mechanism for early exit from iteration — there is no separate break keyword as in C or Java; instead, break! is an iter that immediately terminates by returning without yielding. at_end! is a predicate iterator that is used inside iter routine implementations to check for boundary conditions: if at_end! then return end inside an iter body terminates the iterator if the underlying data structure is exhausted. The retainer work pattern with at_end! is ensuring that iter routines check boundaries before accessing elements, preventing out-of-bounds access on empty or boundary inputs. The most common bug is an iter routine that calls yield before checking at_end!, producing one extra iteration step past the last valid element.
Sather’s loop construct is more powerful than a simple while or for loop. A loop body can contain multiple iterator calls: loop a := list1.elements!; b := list2.elements!; combine(a, b) end — this iterates over both lists in lockstep, terminating when either iterator runs out. Multiple iterators in the same loop create implicit zip semantics: the loop runs as long as all iterators yield, and terminates when any one terminates. The upto!(n) built-in iter produces integers from 0 to n-1: loop i := upto!(10); process(i) end is the Sather equivalent of a for loop from 0 to 9. downto!(n) counts down. The stride!(start, end, step) iter produces arithmetic progressions. These built-in iters compose naturally with user-defined iters in a single loop: loop i := upto!(list.size); elem := list.elements!; store_indexed(i, elem) end iterates with both an index counter and an element iterator simultaneously.
Sather’s type system is statically typed with type inference for local variables. The language uses structural subtyping in some contexts and nominal typing in others. Inclusion polymorphism in Sather is achieved through abstract classes: a class declared as abstract class SHAPE (using the ABSTRACT designation in some Sather dialects) defines an interface of routines that concrete subclasses must implement. A variable of type SHAPE can hold any concrete subclass instance, and routine calls are dispatched to the correct implementation at runtime. The key design principle: Sather’s type dispatch selects the most specific concrete class’s implementation, not based on the declared type of the variable but on the runtime type of the object. This allows the same code to handle all subclasses through the abstract interface without modification when new subclasses are added. The retainer work of replacing typecase-style branching with type dispatch is precisely converting from explicit class-identity checks (which require modification when new subclasses are added) to the inclusion polymorphism model (which requires only the new subclass to implement the abstract interface).
pSather parallel programming, generic classes, pre/post contracts, and C interop
pSather is the parallel extension of Sather designed for distributed-memory and shared-memory parallel computing. The key pSather additions are parallel loop operators: | (data-parallel) and & (task-parallel). A parallel loop using |: loop | elem := list.elements!; process(elem) end executes the body for all elements concurrently in a data-parallel fashion — each element is processed simultaneously on available processors. A task-parallel construct using &: loop & task := tasks.elements!; execute(task) end creates a new parallel task for each iteration body. The distinction: | implies lock-step synchronization (SIMD-style), while & implies fully independent asynchronous tasks. Shared mutable state access in parallel loops must be synchronized explicitly; pSather provides monitors and locks for shared attribute access. The most common pSather retainer bug is a parallel loop with | or & that reads and writes a shared attribute without synchronization, producing non-deterministic results.
Sather supports generic classes with type parameters: class STACK{T} declares a generic stack parameterized by element type T. Inside the class, attributes and routines use T as a type: own storage: ARRAY{T}, push(elem: T), top: T. Instantiation: STACK{INT} creates an integer stack; STACK{STR} creates a string stack. Sather’s generics are parametric (not template-based): a single compiled implementation is shared across all instantiations that have compatible layouts, with type-specialized versions generated for performance-critical primitive types. Type constraints on generic parameters are expressed through where clauses that require specific routine signatures on the type parameter: class SORTED_STACK{T} where T has op_lt: SAME; T requires T to implement the less-than operator.
Sather’s pre/post contract system, inherited from Eiffel’s design-by-contract philosophy, allows routines to declare formal preconditions and postconditions. A pre clause specifies conditions that must hold when the routine is called: routine top: ELT pre is_not_empty declares that top requires the stack to be non-empty. A post clause specifies conditions that must hold when the routine returns: routine push(e: ELT) post size = old_size + 1 declares that pushing increases the size by one. In debug builds, Sather checks pre and post conditions at runtime and reports violations. In release builds, checks are often disabled for performance. The retainer work around contracts is authoring correct preconditions that match the actual caller obligations (so that violations correctly identify caller bugs), postconditions that match the actual guarantees (so that violations correctly identify implementation bugs), and designing class invariants that hold across all routine calls. The old keyword in postconditions refers to the value of an expression at routine entry: post size = old size + 1 uses old size to refer to the stack size before the push.
Sather C interop uses SYS class primitives. SYS.get_global(name, type) retrieves a C global variable by name with a Sather type annotation. External C routines are declared with extern declarations that specify the C function name, parameter types, and return type. Sather uses a foreign function interface (FFI) that maps Sather primitive types to C types: INT maps to int, FLT maps to float, DBLE maps to double, STR maps to char*. The inti, dble, and flt type coercions handle numeric conversions at FFI boundaries. The retainer work around C interop is designing correct type mappings (particularly for pointer types and struct layouts), managing memory ownership at the Sather/C boundary (Sather’s garbage collector must not reclaim objects that C code holds references to), and designing wrapper routines that provide clean Sather interfaces over C library functions.
How HourTab tracks Sather developer retainer hours
Sather retainer work shares the invisible-work problem with all statically typed OOP language retainers, with the additional challenge that Sather’s most important retainer tasks — own attribute redeclaration for per-instance state isolation, iter yield/break!/at_end! iterator design, type dispatch design for inclusion polymorphism, and pSather parallel loop synchronization — produce diffs whose surface area is small relative to the analytical work required. Adding own count: INT := 0 to a subclass body is a diff with one line; the value is correct per-instance attribute isolation for all instances of that subclass, elimination of shared-state corruption across all concurrent instances, and a class hierarchy that correctly expresses independent per-instance counters. Designing three separate break-guarded iter routines from a single unsafe index-based loop is a diff with twenty lines; the value is correct boundary checking for all inputs including empty collections, elimination of out-of-bounds attribute access on boundary inputs, and iterator routines that compose correctly in multi-iterator loop contexts. Redesigning a ten-branch typecase into a type-dispatched abstract class hierarchy is a diff across eight files; the value is correct runtime dispatch to the most specific implementation, elimination of all callers needing modification when new subclasses are added, and correct Sather type system guarantees that all abstract routine requirements are implemented.
HourTab gives Sather developers a public retainer-hours URL they send to clients — typically compiler engineering teams using Sather as a systems programming research language, OOP theory researchers using Sather as a formal model for object-oriented semantics, and performance-engineering teams using pSather for parallel computation — at the start of an engagement. For Sather retainers, each work log entry should name the mechanism (own vs shared attribute audit in class hierarchy; := mutation vs functional set_foo update discipline; init routine design for per-instance attribute initialization; iter yield routine design; break! early termination sentinel design; at_end! boundary predicate design; loop construct design with correct type annotations; inclusion polymorphism via type-based dispatch design; abstract class interface design; concrete subclass routine implementation; generic class {T} type parameter design; pre/post contract clause design; | data-parallel loop body design; & task-parallel execution design; SYS.get_global C interop binding; inti/dble/flt type coercion; res return value design), the specific class name and the attribute ownership problem or iterator boundary issue, and the before/after observable metric. Sather retainers are often compared to Smalltalk developer retainers for OOP system design work and to Clean developer retainers for functional-style type discipline in a systems language. The distinction from Smalltalk is Sather’s static type system and explicit own/shared distinction: in Smalltalk, instance variables are always per-instance; in Sather, the programmer must explicitly declare which kind is needed. HourTab’s work log makes the attribute ownership audit and iterator protocol design visible to clients who would otherwise see only the symptom — shared-state corruption across all instances — and not understand why the fix required understanding Sather’s explicit attribute ownership model and the own redeclaration requirement.
Track Sather developer retainer hours without the status emails
HourTab gives Sather 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: Sather developer retainers
What does a Sather developer on retainer typically do?
A Sather developer on monthly retainer covers four principal service areas: attribute declaration design (own vs shared attribute audit in class hierarchies; := mutation vs functional set_foo update discipline; init routine design for per-instance attribute initialization; multi-level hierarchy attribute shadowing analysis; precall init superclass initialization chaining); iteration protocol design (iter yield routine authorship; break! early termination sentinel; at_end! boundary predicate; loop construct with multiple iterator composition; upto!/downto!/stride! built-in iters); type system and polymorphism engineering (inclusion polymorphism via type-based dispatch; abstract class interface design; concrete subclass routine implementation; generic class {T} type parameter design; pre/post contract design); and pSather parallel programming (| data-parallel loop body design; & task-parallel execution; shared state synchronization; SYS.get_global C interop; inti/dble/flt type coercions).
What Sather work is most commonly underlogged in a retainer?
Own attribute declaration repair (RATE_COUNTER subclass missing own count: INT override; count := count + 1 updated COUNTER ancestor attribute; state corruptions: 6/session → 0 after own count: INT := 0 added; 12–21 hrs invisible in class hierarchy audit, attribute dispatching rule analysis, and init routine restructuring), iterator protocol design (index-based list iteration without at_end! predicate causing out-of-bounds access on boundary inputs; rewritten as iter routine with at_end!/break! guards; out-of-bounds accesses: 3/run → 0; 9–16 hrs invisible in iter routine design and loop consumer restructuring), and inclusion polymorphism design (typecase-style branching requiring modification per new subclass; restructured using type dispatch with abstract class; dispatch maintenance overhead: eliminated; 10–18 hrs invisible in type dispatch design, abstract interface design, and subclass routine authorship).
What are typical Sather developer retainer rates?
Entry-level Sather developers (1–2 years, class/subclass declaration, basic attribute := mutation, simple routine definitions, built-in iteration) bill at $60–$110/hr. Mid-level Sather engineers (2–4 years, own vs shared attribute audit, iter yield/break!/at_end! design, inclusion polymorphism via type dispatch, generic class {T} design, pre/post contract design, C interop via SYS.get_global) bill at $105–$185/hr. Senior Sather architects (4–8 years, pSather parallel loops, distributed object systems, abstract class hierarchy design, complex generic type constraints, Sather compiler toolchain, performance optimization) bill at $150–$275/hr. Monthly retainer ranges: $2,000–$4,500/mo advisory (15–25 hrs), $6,500–$16,000/mo for full Sather platform engagements.
What should a Sather developer retainer agreement include?
A Sather developer retainer agreement should specify: attribute design scope (own vs shared attribute audit; := mutation vs set_foo update; init routine design; multi-level hierarchy attribute shadowing; precall init superclass chaining); iteration scope (iter yield routine design; break! early termination; at_end! boundary predicate; loop multi-iterator composition; upto!/downto!/stride! built-ins); type system scope (inclusion polymorphism via type dispatch; abstract class interface; concrete subclass routines; generic class {T}; pre/post contract clauses; old in postconditions); pSather scope (| data-parallel loops; & task-parallel; shared state synchronization; distributed object creation; race condition audit); C interop scope (SYS.get_global binding; extern routine declarations; inti/dble/flt coercions; FFI type mapping; memory ownership); and hour logging format (class name; attribute type — own or shared; iterator or loop type; before/after corruption metric; Sather version).
How should Sather developer retainer hours be logged?
Log each Sather retainer session with: advisory category (own vs shared attribute audit; := mutation vs functional set_foo; init routine design; attribute shadowing analysis; iter yield routine design; break! early termination; at_end! boundary predicate; loop multi-iterator design; inclusion polymorphism via type dispatch; abstract class interface; generic class {T} design; pre/post contract design; | data-parallel loop; & task-parallel; SYS.get_global C interop; inti/dble/flt coercion; res return value design), the specific class name and attribute ownership problem (RATE_COUNTER subclass missing own count: INT; count := count + 1 updated COUNTER ancestor; state corruptions: 6/session → 0 after own count: INT := 0 added), and the before/after metric (state corruptions/session: 6 → 0; out-of-bounds accesses/run: 3 → 0; dispatch maintenance changes per new subclass: N → 0). Include Sather/pSather version, OS, and whether the fix required own attribute declaration, iter routine authorship, type dispatch restructuring, or parallel loop synchronization.