Blog › ICP guides

Logtalk developer on retainer: self/1 vs this/1 message dispatch, Logtalk object model, mixin categories, prototype hierarchy, and Logtalk-Prolog integration on monthly retainer

November 18, 2026 · ~16 min read

A Logtalk program defining a mixin category composed into a prototype hierarchy was producing five wrong helper call results per run. The program had a base prototype shape with a render/0 predicate and a helper/0 predicate; a derived prototype styled_shape that extended shape and overrode helper/0 with a styled variant; and a category renderer_mixin that provided render/0 and a separate log_render/0 predicate, both imported by styled_shape. The mixin’s render/0 clause called ::helper (shorthand for self::helper) — correctly dispatching the helper message to the current receiver, which would use styled_shape’s overridden helper/0. This worked as intended. The failure was in log_render/0, where the developer intended to call the mixin’s own internal helper/0 — the version defined on the category itself, bypassing any override in the receiving prototype. The developer wrote ::helper in log_render/0 as well, assuming it would dispatch to the category’s local predicate. In Logtalk, ::Message (or equivalently self::Message) sends the message to the current receiver — the object that received the message that created the current execution context, regardless of which object or category defines the clause currently being executed. When log_render/0 was called on a styled_shape instance, self was bound to styled_shape, and ::helper dispatched to styled_shape’s overridden helper/0 rather than the category’s own predicate. Five wrong helper calls per run. The Logtalk developer on retainer diagnosed the self/1 vs this/1 confusion: restructured log_render/0 to call this(Category), Category::helper — where this/1 unifies with the entity currently defining the executing clause (the category itself), so Category::helper dispatches directly to the category’s own predicate, bypassing the receiver’s prototype chain. Wrong helper calls per run: 5 → 0.

The work log entry read “fixed wrong helper dispatch, 16h.” It names the symptom and duration. It cannot explain to a client why self/1 in Logtalk gives the current receiver (the prototype that received the outermost message in the current execution context — not the object defining the clause, not an intermediate object in a delegation chain, but the original message target whose prototype chain the current lookup traversed to find the executing clause) while this/1 gives the current defining entity (the object, class, prototype, or category whose clause body is currently executing — if execution entered through a renderer_mixin clause, this is the renderer_mixin category, regardless of which prototype received the original message). It cannot explain why the distinction matters specifically in category composition: in a prototype hierarchy with no categories, calling self::M and this(T), T::M from a prototype method produce the same result only when the prototype has no override of M above its own definition; with a category, the category is not in the prototype’s hierarchy chain, so there is never a “same result” case — self::M always dispatches through the receiver’s chain (potentially finding an override), and this(C), C::M always dispatches to the category’s own clause (the defining entity, not in the receiver’s chain). It cannot explain why ^^M (Logtalk’s super-send notation) was not the right fix here: ^^M dispatches to the next definition of M above the current execution context in the hierarchy, used for calling an overridden predicate’s parent implementation; the category’s helper was not overriding anything in the receiver’s hierarchy — it was a distinct local predicate that happened to share a name, making this(C), C::M the correct tool and ^^M semantically wrong. The 16 hours of execution context analysis, call site audit across all category clauses to distinguish receiver-dispatch from defining-entity dispatch, and systematic this/1 refactoring at every intra-category dispatch site are invisible in the diff beyond the substituted this(Category), Category::helper call.

Logtalk execution context: self/1, this/1, sender/1, and message-sending operators

Logtalk’s execution model gives every predicate clause access to its full execution context through four meta-predicates. self/1 unifies its argument with the current receiver — the object that received the message that created the current execution context. When a message is sent with Object::Message, Object becomes the receiver; self in any clause executed as a result of that message (including clauses in superprototypes, imported categories, or any clause reached by delegation) is bound to that original Object. this/1 unifies with the entity defining the current clause — the object, category, class, or instance whose source file contains the clause body currently being executed; it changes as execution crosses from one entity’s clause into another. sender/1 unifies with the object that sent the message that created the current execution context — the object that wrote Object::Message. parameter/2 applies to parametric objects and retrieves the Nth parameter of the current parametric entity.

The message-sending operators encode the dispatch semantics explicitly. Object::Message sends Message to Object; Object is the receiver; lookup starts at Object and traverses its prototype chain (or class hierarchy) to find the predicate. ::Message sends Message to self (the current receiver); equivalent to self(S), S::Message but uses the already-known receiver binding. ^^Message (super-send) sends Message to the ancestor above the defining entity (not above the receiver); used in a prototype method to call the overridden version in the extended prototype, or in a class method to call the specialized superclass version. this(C), C::Message dispatches to the entity currently defining the executing clause; when used inside a category, C unifies with the category name, and C::Message dispatches to the category’s own clause for Message, bypassing the receiver’s prototype chain entirely. The retainer rule: audit every ::M call inside category clauses; if the intent is intra-category dispatch to the category’s own predicate (not the receiver’s override), replace with this(C), C::M; if the intent is receiver-chain dispatch (the overrideable pattern), keep ::M.

The execution context is threaded through Logtalk’s compilation to Prolog. Logtalk compiles each message send into a Prolog goal that passes the execution context as implicit hidden arguments. Every Logtalk predicate clause compiles to a Prolog clause with additional arguments encoding sender, this, self, and meta-call context. This is transparent to Logtalk source code but becomes visible in two situations: when using the ISO Prolog call/N directly on Logtalk goals (instead of using Logtalk’s call//N or the Logtalk meta-predicate facility), and when debugging using the Logtalk tracer, which shows the execution context alongside each goal. For Logtalk meta-predicates (predicates that receive goal arguments and call them), a meta_predicate directive must be declared; without it, the Logtalk compiler does not thread the execution context through the meta-call, and self and this in the called goal may resolve to wrong entities.

Logtalk categories: declaration, object imports, dispatch ordering, and meta-predicate context

A Logtalk category is a named, reusable collection of predicate clauses that can be imported by any object. Categories are analogous to Java interfaces with default implementations, Ruby modules, or Haskell typeclasses with default method bodies. A category is declared with the :- category(Name) directive and closed with :- end_category. An object imports a category with the implements (for protocols), imports, or the combined object(Name, imports(Category)) form. An object can import multiple categories: :- object(styled_shape, extends(shape), imports(renderer_mixin, logger_mixin)). When a message is sent to styled_shape, Logtalk’s lookup order is: (1) styled_shape’s own predicate clauses; (2) predicate clauses from imported categories (renderer_mixin, logger_mixin) in import order; (3) predicate clauses from extended prototypes (shape and its ancestors). Categories are not in the prototype chain — they are a separate import mechanism, and their clauses execute with this bound to the category, not to the importing object.

Meta-predicates in categories require explicit :- meta_predicate declarations. A category predicate that receives a goal argument and calls it via call/N must declare the goal argument’s arity in the meta-predicate directive: :- meta_predicate maplist(1, *) declares that the first argument of maplist/2 is a goal of arity 1 (a closure expecting one more argument) and the second is a plain term. Without this declaration, the Logtalk compiler does not know that the first argument is a goal to be called with an execution context; it compiles the call/1 on the argument without threading the current execution context, and self in the called goal resolves to the wrong object. The fix is always: add the :- meta_predicate directive matching the predicate signature. Retainer audit pattern: for every category predicate that uses call/N with an argument received from the caller, verify that a matching :- meta_predicate directive exists in the category.

Categories can also declare protocol conformance and define abstract predicate interfaces. A category can include :- use_module (for host Prolog modules) or :- uses(Object, [pred/1]) to reference predicates from other Logtalk objects or Prolog modules without explicit object prefixes in every call. The :- uses directive in a category applies only within that category’s clause bodies; the importing object does not automatically inherit the uses shorthands. Retainer pattern: when a category is imported by an object that also uses a same-named predicate from a different source, the uses directive scoping prevents name collisions silently — verify that all unqualified calls in category clauses resolve to the intended source by checking the category’s own uses directives.

Logtalk prototype hierarchy, class-instance model, and Paulo Moura’s design

Logtalk supports two independent object hierarchy models that can coexist in the same system. In the prototype-based model, objects directly extend other objects: :- object(styled_shape, extends(shape)). Prototype chains are traversed by extends/1 links; there is no class/instance distinction. This model suits configuration hierarchies, template objects, and systems where behavior differences between objects are gradual. In the class-instance model, classes specialize other classes (:- object(ColoredShape, specializes(Shape), instantiates(class))) and objects are instances of classes (:- object(red_circle, instantiates(ColoredShape))). The class hierarchy is traversed for method lookup; instances hold state, classes hold behavior. Logtalk allows both models in the same Logtalk image, and objects can play multiple roles (an object can be simultaneously a class and an instance of a metaclass). The retainer pattern for hierarchy bugs: determine which model an object uses before reading method lookup rules — extends for prototype chains, instantiates/specializes for class-instance chains; mixed models have their own lookup rules that differ from both pure forms.

Paulo Moura designed Logtalk as a superset of ISO Prolog that adds encapsulation, polymorphism, and code reuse through object-oriented mechanisms while remaining compilable by any ISO Prolog system. The implementation strategy is term expansion: the Logtalk compiler reads Logtalk source and emits standard ISO Prolog terms that encode the object model; these terms are loaded by the host Prolog system (SWI-Prolog, SICStus Prolog, YAP, Ciao, ECLiPSe, GNU Prolog, and others). This means Logtalk inherits the host Prolog’s debugger, module system, constraint libraries, and foreign function interface — a Logtalk object can freely call host Prolog predicates via the :- use_module or direct module-qualified call syntax. The tradeoff is that Logtalk’s compilation output is host-Prolog-dependent: code compiled under SWI-Prolog may have different term shapes than under SICStus, and the Logtalk runtime predicates themselves are Prolog clauses that are visible to the host Prolog’s debugger, which can make step-through debugging of Logtalk programs show the compilation artifacts rather than the Logtalk source. The Logtalk developer-mode flag enables source-level debugging through Logtalk’s own tracer, which presents goals in Logtalk source form with execution context.

Logtalk’s event system allows objects to subscribe to message-send events: :- define_events(before, Object, Message, Sender, Monitor) registers a monitor to be called before a specific message is sent to a specific object. Events are useful for aspect-oriented patterns (logging all sends of a particular message without modifying the target object), debugging (tracing which objects are sending which messages), and instrumentation (counting message send frequency for performance profiling). Event handling has a non-trivial performance cost because every message send must check whether any event monitors are registered; for performance-critical Logtalk systems, events are enabled only during development and testing, not in production deployments. Retainer work on Logtalk event systems: verify that event subscriptions are properly scoped and unregistered when the monitor object is no longer in use; leaked event subscriptions that survive their intended scope cause unexpected callbacks and are a source of behavior that appears after components are supposedly shut down.

How HourTab tracks Logtalk developer retainer hours

Logtalk retainer work shares the invisible-work problem common to all logic programming retainers, compounded by Logtalk’s execution context model — a ::helper call and a this(C), C::helper call look nearly identical in a diff but have completely different dispatch semantics, and the wrong choice produces wrong results only when the receiving object has an override for helper in its prototype chain; when there is no override (the common case during development, before overrides are added), both calls produce correct results and the bug is invisible until the override is introduced. A self/1 vs this/1 dispatch repair is a diff with one replaced predicate call; the value is correct dispatch for all receiving objects including those with overrides, a systematic audit of all ::M calls in category clauses to verify that receiver-chain dispatch was the intent at each call site, and a documented category API that distinguishes receiver-dispatch predicates from defining-entity-dispatch predicates so that future contributors know which pattern applies where.

HourTab gives Logtalk developers a public retainer-hours URL they send to clients — typically organizations building knowledge representation systems in Logtalk over a Prolog backend, research teams using Logtalk’s object model for agent simulation frameworks, and groups maintaining large Prolog codebases that are being refactored into Logtalk objects for encapsulation. For Logtalk retainers, each work log entry should name the mechanism (self/1 vs this/1 execution context analysis; ::Message receiver-chain dispatch; this(C), C::Message defining-entity dispatch; ^^Message super-send positioning; meta_predicate declaration audit; prototype hierarchy delegation chain repair; class-instance lookup order analysis; event subscription scoping; host Prolog module interaction), the specific objects, categories, and dispatch chains involved, and the before/after metric. Logtalk retainers are often compared to Prolog developer retainers for the shared ISO Prolog foundation and to Mercury developer retainers for the similar logic-programming object-model and dispatch hierarchy design challenges. HourTab’s work log makes the execution context analysis, call site audit, and this/1 refactoring discipline visible to clients who would otherwise see only the symptom — five wrong helper calls per run — and not understand why the fix required understanding Logtalk’s execution context model, distinguishing receiver-dispatch from defining-entity dispatch at each category call site, and substituting this(Category), Category::helper at every intra-category dispatch that was incorrectly using the receiver-dispatch form.

Track Logtalk developer retainer hours without the status emails

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

What does a Logtalk developer on retainer typically do?

A Logtalk developer on monthly retainer covers four principal service areas: message dispatch design (self/1 vs this/1 execution context analysis; ::Message receiver-chain dispatch; this(C), C::Message defining-entity dispatch; ^^Message super-send positioning; message sending operator selection per call site); category and mixin composition (category declaration and object imports; dispatch ordering: own predicates → imported categories → extended prototypes; meta-predicate declarations; execution context threading through call/N); prototype and class-instance hierarchy engineering (extends/1 prototype chain; instantiates/1, specializes/1 class hierarchy; method lookup in both models); and Logtalk-Prolog integration (Logtalk compilation to ISO Prolog term expansions; host Prolog selection; module system interaction; ISO Prolog built-in predicate access).

What Logtalk work is most commonly underlogged in a retainer?

Self/1 vs this/1 dispatch repair (mixin category called self::helper intending category’s own helper; self dispatched to receiver’s override when one existed; 5 wrong calls per run; restructured to this(C), C::helper; wrong calls: 5/run → 0; 14–22 hrs invisible in execution context analysis and call site audit); meta-predicate execution context threading (category predicate received goal argument and called via call/1; missing meta_predicate declaration caused Logtalk compiler to not thread execution context; self in called goal resolved to wrong object; 6–9 hrs invisible in meta_predicate declaration audit); and delegation chain debugging (message traversed extends chain; intermediate prototype caught message but called ^^ incorrectly, short-circuiting chain; 4–8 hrs invisible in hierarchy traversal analysis).

What are typical Logtalk developer retainer rates?

Entry-level Logtalk developers (1–2 years, basic object declarations, message sending, prototype hierarchy) bill at $65–$115/hr. Mid-level Logtalk engineers (2–4 years, self/1 vs this/1 analysis, category mixin composition, meta-predicate declarations, ISO Prolog host integration) bill at $110–$190/hr. Senior Logtalk architects (4–8 years, full object model including class-instance hierarchies, parametric objects, lambda expressions, event system, multi-threading) bill at $165–$290/hr. Monthly retainer ranges: $1,800–$4,800/mo advisory (15–25 hrs), $6,500–$18,000/mo for full Logtalk system development engagements.

What should a Logtalk developer retainer agreement include?

A Logtalk developer retainer agreement should specify: message dispatch scope (self/1 vs this/1 execution context analysis; ::Message receiver-chain dispatch; this(C), C::Message defining-entity dispatch; ^^Message super-send); category composition scope (category declaration; object imports; dispatch ordering; meta-predicate declarations; execution context threading); prototype hierarchy scope (extends/1 hierarchy design; method lookup; delegation chain debugging); class-instance hierarchy scope (instantiates/1, specializes/1; instance creation; class-side vs instance-side predicates); and hour logging format (advisory category, before/after wrong-dispatch metric, Logtalk version and host Prolog, whether fix required this/1 substitution, meta_predicate declaration addition, ^^/2 super-send repositioning, or category imports restructuring).

How should Logtalk developer retainer hours be logged?

Log each Logtalk retainer session with: advisory category (self/1 vs this/1 execution context analysis; ::Message receiver-chain dispatch; this(C), C::Message defining-entity dispatch; ^^Message super-send; message sending operator selection; category declaration and object imports; meta-predicate declarations; execution context threading through call/N; prototype hierarchy design; class-instance hierarchy design; Logtalk-Prolog host integration); the specific objects, categories, predicates, and dispatch chains involved (mixin category called self::helper; receiver prototype had override; self dispatched to override; 5 wrong calls per run; restructured to this(C), C::helper; wrong calls: 5/run → 0); and the before/after observable metric. Include Logtalk version, host Prolog, and whether fix required this/1 substitution, meta_predicate declaration addition, ^^/2 super-send repositioning, or category imports restructuring.