Blog › ICP guides
Mercury developer on retainer: determinism system, mode declarations, det vs semidet vs nondet, module system, and Mercury logic programming on monthly retainer
December 1, 2026 · ~15 min read
A Mercury program implementing a data classification system defined a predicate classify(Input::in, Category::out) declared with determinism det — meaning the predicate must succeed with exactly one solution for every input. The predicate had two clauses: classify(Input, category_a) :- matches_pattern_a(Input). and classify(Input, category_b) :- matches_pattern_b(Input).. The developer tested the predicates matches_pattern_a and matches_pattern_b separately and observed they both succeeded for certain inputs — some data items legitimately matched both patterns. Mercury’s determinism checker rejected the predicate as declared: a det predicate must succeed with exactly one solution for any input satisfying the mode constraints, but the two clauses without disjointness guards could both succeed for the same input, producing two solutions. The predicate was nondeterministic (nondet), not deterministic (det). Two determinism errors per predicate declaration. The Mercury developer on retainer diagnosed the determinism mismatch: the developer had assumed that because each individual clause was deterministic (each would produce exactly one category), the predicate was deterministic. But Mercury’s determinism analysis considers all clauses together: if multiple clauses can succeed for the same input, the predicate has more solutions than det allows. The fix required adding disjoint guards to each clause to ensure that at most one clause can succeed for any given input. For the classification case, this meant adding a negation guard to one of the clauses: classify(Input, category_a) :- matches_pattern_a(Input), not(matches_pattern_b(Input)). or restructuring with an explicit if-then-else: classify(Input, Cat) :- (matches_pattern_a(Input) -> Cat = category_a ; Cat = category_b).. With the if-then-else form, Mercury’s determinism analysis could verify that exactly one branch executes per input. Determinism errors: 2 per predicate → 0.
The work log entry read “fixed classification predicate determinism, 6h.” It names the outcome and duration. It cannot explain why Mercury’s determinism checker rejects clauses that can both succeed — in Prolog, having multiple clauses is standard and the programmer manages nondeterminism through cut or control flow; in Mercury, the declared determinism is a contract that the compiler verifies statically, and two unguarded clauses that can both succeed is a violation of the det contract regardless of whether the programmer intends to use only the first solution. It cannot explain why the if-then-else form satisfies the determinism checker while two independent clauses do not — if-then-else in Mercury is deterministic by structure: the condition is tested once, exactly one branch executes, and the determinism of the whole expression is the combination of the condition determinism and the chosen branch determinism; two independent clauses have no such structural guarantee of disjointness. It cannot explain the retainer’s decision to use if-then-else over negation guards for the fix — if-then-else produces cleaner determinism analysis (the compiler can confirm at each branch that the other branch cannot also succeed), while negation guards require the compiler to track that not(matches_pattern_b) and matches_pattern_b are disjoint, which may require additional annotations. The 6 hours of determinism semantics analysis, clause disjointness evaluation, and restructuring decision are invisible in the diff.
Mercury determinism system: det, semidet, multi, nondet, erroneous, and failure
Mercury’s determinism system is a static type-like discipline that classifies each predicate by two orthogonal properties: the minimum number of solutions it produces and the maximum number. The minimum can be zero (the predicate may fail) or at least one (the predicate always succeeds on inputs satisfying the mode). The maximum can be one (at most one solution) or more (backtracking is possible). The six determinism categories are: det (exactly one solution: minimum one, maximum one), semidet (zero or one solution: minimum zero, maximum one), multi (one or more solutions: minimum one, maximum many), nondet (zero or more solutions: minimum zero, maximum many), erroneous (the predicate never returns — it always throws an exception), and failure (the predicate always fails). These determinism categories propagate through predicate composition: a predicate whose body calls a nondet subgoal is at least nondet; a predicate whose body calls two det subgoals in conjunction is det; the determinism of conjunction is determined by the maxima and minima of the components.
The determinism category must be declared explicitly for each predicate mode (or inferred if not declared, subject to compiler inference limits). A predicate declared det is a promise that the compiler verifies: it will reject the declaration if the implementation’s determinism analysis produces a less deterministic category. The analysis considers all clauses: if two clauses can both succeed, the predicate is at least multi or nondet, not det or semidet. The disjointness of clauses must be provable from guards and conditions that Mercury can analyze statically; arbitrary Prolog-style nondeterminism managed through cut is not available in Mercury. The determinism system’s value is that it makes solution count a verified contract: callers that aggregate all solutions (using solutions/2 or aggregate/4) know they will get at least one; callers that use the predicate deterministically know exactly one execution path will be taken. This predictability is essential for Mercury’s performance model, which compiles deterministic predicates to straightforward imperative code without any backtracking infrastructure.
The det vs semidet choice is a design decision with performance implications. A det predicate always succeeds and produces exactly one solution; Mercury can compile it to code that does not check for failure. A semidet predicate may fail, so Mercury must generate code that checks the success indicator and propagates failure to the caller. For predicates that perform lookups (find an element if it exists), semidet is usually correct — the element may not be present. For predicates that classify inputs into categories (every input belongs to exactly one category), det is correct — but requires that the clauses partition the input space completely and disjointly. Getting this classification right requires careful design of the clause guards, which is frequently the work that consumes retainer hours.
Mercury mode system: in, out, di, uo, multiple mode declarations, and mode checking
Mercury’s mode system tracks the instantiation state of each argument at the call site and the exit of a predicate. Each argument has a mode: in means the argument is ground (fully instantiated) at the call site, out means the argument is unbound at the call site and ground at the exit. The mode declaration :- mode pred_name(in, in, out) is det. specifies that the first two arguments must be ground when the predicate is called, and the predicate will bind the third argument and succeed with exactly one solution. Mode checking is distinct from type checking: the type tells you what kind of values an argument can hold; the mode tells you in which direction the argument flows information at this particular call site.
A single predicate may be called with different instantiation patterns at different call sites. For example, a lookup(Key, Table, Value) predicate might be called with Key and Table known (looking up a value), or with Value and Table known (finding keys associated with a value). These are different modes: :- mode lookup(in, in, out) is semidet. and :- mode lookup(out, in, in) is nondet.. Mercury supports multiple mode declarations for the same predicate, and the compiler generates separate code for each mode. A predicate called at a call site that does not match any declared mode is a compile error. Retainer work involving mode system issues often involves adding mode declarations for calling patterns that the original developer did not anticipate, or restructuring predicates whose natural logic does not fit any efficiently-decidable mode.
The di and uo mode annotations are analogous to Clean’s unique type annotations, applied within Mercury’s mode system. di (destructive input) declares that an argument is unique at the call site and will be consumed; uo (unique output) declares that the output is a unique value. These modes enable in-place mutation for I/O and other stateful operations. Mercury’s io.T type (the I/O state) always uses di/uo modes: every I/O operation takes the current I/O state as di (destructive input, consuming the old state) and returns a new I/O state as uo (unique output). This sequencing mechanism ensures I/O operations happen in a deterministic order without using a World token explicitly, encoding the sequencing constraint in the mode system. Mercury was developed at the University of Melbourne by Fergus Henderson, Thomas Conway, Zoltan Somogyi, and others in the mid-1990s; it is a logic programming language with a Prolog-like syntax but a type system, mode system, and determinism system that enforce static guarantees absent from standard Prolog. Its closest retainer-ecosystem relatives are Prolog (for the logic programming heritage and unification-based computation) and Logtalk (for the object-oriented logic programming layer on top of Prolog-family runtimes), but Mercury’s static determinism verification, mode declarations, and compilation to efficient native code through C make the retainer work distinct in determinism analysis, mode declaration design, and logic program architecture.
How HourTab tracks Mercury developer retainer hours
Mercury retainer work carries the invisible-hours problem common to all statically-typed logic programming language retainers, amplified by the mismatch between Mercury’s Prolog-like syntax and its fundamentally different static verification requirements. Teams using Mercury for high-performance logic programming, verified computation, or compiler and analysis tool development frequently encounter the determinism mismatch pattern described above: a developer writes two clauses that feel disjoint in intent but lack formal guards that Mercury’s determinism analysis can verify statically. The 2 determinism errors per predicate described above is one instance of a broader pattern; the retainer work is the determinism semantics analysis that explains why unguarded parallel clauses produce nondeterminism, the clause disjointness evaluation that determines whether the clauses are truly disjoint or can overlap, the restructuring decision that chooses between if-then-else and negation guards, and the mode declaration engineering that specifies the correct calling patterns for each predicate. Mercury retainers produce visible outcomes — determinism errors: 2 per predicate → 0; mode checking errors at call sites: N → 0 — but the hours spent on determinism category selection (det vs semidet vs multi; what the correct solution count contract should be), clause disjointness analysis (can both clauses succeed? under what inputs?), mode declaration engineering (which calling patterns exist? does the predicate’s logic support each mode?), and determinism propagation analysis (how does a nondet subgoal affect the enclosing predicate’s determinism?) appear in work logs as “fixed determinism error” without explaining the static verification mechanics.
HourTab gives Mercury developers a public retainer-hours URL they send to clients — typically academic research groups using Mercury for verified software or program analysis tools, organizations maintaining Mercury codebases built for high-performance logic computation, and developers migrating from Prolog who need guidance on Mercury’s determinism and mode requirements. For Mercury retainers, each work log entry should name the mechanism (determinism mismatch repair: det predicate with non-disjoint clauses restructured with if-then-else or negation guards; mode declaration addition: multiple modes declared for different calling patterns at different call sites; determinism category selection: multi vs nondet vs det based on solution count contract; di/uo mode annotation for I/O state sequencing; foreign language interface mode annotation), the specific predicate names, mode declarations, determinism categories, and error counts involved, and the before/after metric. Mercury retainers are often compared to Prolog developer retainers for the shared logic programming heritage, but Mercury’s static determinism verification, mode checking at call sites, compilation to efficient native code, and fundamentally different correctness guarantees make the retainer work distinct in determinism analysis, mode declaration design, and disjoint clause guard engineering. HourTab’s work log makes the determinism category analysis, clause disjointness evaluation, and mode declaration decisions visible to clients who would otherwise see only the symptom — a determinism checker error — and not understand why two clauses that feel obviously disjoint to the programmer require explicit formal guards before Mercury’s static analysis can verify that the predicate produces exactly one solution.
Track Mercury developer retainer hours without the status emails
HourTab gives Mercury 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 determinism engineering log becomes the proof of value that gets the retainer renewed.
See HourTab pricing →FAQ: Mercury developer retainers
What does a Mercury developer on retainer typically do?
A Mercury developer on monthly retainer covers Mercury determinism system (det: exactly one solution; semidet: zero or one solution; multi: one or more solutions; nondet: zero or more solutions; erroneous/failure for bottom predicates; determinism inference), Mercury mode system (:- mode declaration syntax; in/out/di/uo instantiation states; multiple mode declarations; mode checking at call sites), Mercury module system (module declaration; import_module; :- pred, :- mode, :- type, :- typeclass, :- instance declarations), and Mercury foreign language interface.
What Mercury work is most commonly underlogged in a retainer?
Determinism mismatch repair (det predicate with two clauses that can both succeed; determinism checker rejected as nondeterministic; disjoint guards added ensuring at most one clause matches per input; errors: 2 per predicate → 0; 6–10 hrs invisible); mode declaration addition (predicate called with different instantiation patterns; single mode insufficient; multiple :- mode declarations added for each calling pattern; errors: 3 per call site → 0; 5–9 hrs invisible); determinism category selection (multi vs nondet confusion; wrong category caused compiler errors at aggregation call sites; correct category restores compilation; 4–8 hrs invisible).
What are typical Mercury developer retainer rates?
Entry-level Mercury developers (1–2 years, Mercury basics, determinism and mode basics, Mercury standard library) bill at $70–$125/hr. Mid-level Mercury logic programmers (2–4 years, determinism system design, mode declaration engineering, module architecture, constraint programming) bill at $120–$200/hr. Senior Mercury architects (4–8 years, large-scale deterministic logic design, advanced mode/determinism analysis, foreign language interface, tabling optimization) bill at $170–$295/hr. Monthly retainer ranges: $2,000–$4,900/mo advisory (15–25 hrs), $6,800–$18,000/mo for full Mercury systems development engagements.
What should a Mercury developer retainer agreement include?
A Mercury developer retainer agreement should specify: determinism system scope (det, semidet, multi, nondet, erroneous, failure; determinism inference; disjoint clause guards); mode system scope (:- mode declaration syntax; in/out/di/uo instantiation states; multiple mode declarations; mode checking at call sites); module system scope (module declaration; import_module; pred, mode, type, typeclass, instance declarations); constraint programming scope (CLP(FD), CLP(R) if required); and hour logging format (advisory category, before/after determinism error count, whether fix required disjoint guards, mode declaration addition, or determinism category change).
How should Mercury developer retainer hours be logged?
Log each Mercury retainer session with: advisory category (determinism mismatch repair: det predicate with non-disjoint clauses restructured with disjoint guards; mode declaration addition: multiple modes declared for different calling patterns; determinism category selection: multi vs nondet vs det based on solution count guarantee; di/uo annotation for I/O state sequencing; foreign language interface mode annotation); the specific predicate names, mode declarations, determinism categories, and error counts involved (classify(Input::in, Category::out) is det with two non-disjoint clauses; determinism error: predicate can produce more than one solution; if-then-else restructure added; errors: 2 per predicate → 0); and the before/after metric. Include whether fix required disjoint guards, mode declaration addition, or determinism category change.