Blog › ICP guides
Pop-11 developer on retainer: pattern language, matches, ?x and ??x variables, type restrictions, and Poplog AI programming on monthly retainer
December 11, 2026 · ~15 min read
A Pop-11 program processing structured data used the matches operator to filter lists containing only integers. The developer wrote list matches [??x:isinteger], expecting the pattern to bind x to the list contents only when every element was an integer. In Pop-11’s pattern language, ??x is a rest variable: it matches zero or more consecutive items. The type restriction :isinteger constrains each item matched by ??x to satisfy the predicate. The developer assumed this meant the pattern would fail on lists containing non-integers. Instead, the pattern succeeded on any list, including [hello world] and [], because ??x with :isinteger matches zero items on failure to satisfy the constraint — the pattern matches with x bound to an empty list when no integer prefix is found, rather than failing. Wrong matches: 3 per list processed. The developer restructured using a validation step after matching: list matches [??x] and forall(x, isinteger), which first matches the entire list into x (always succeeds for any list), then applies forall to verify every element satisfies isinteger. Alternatively, using a recursive procedure that pattern-matches one element at a time with ?x:isinteger (single-item type restriction) across the list. Wrong matches: 3/list → 0. The Pop-11 developer on retainer diagnosed the pattern semantics mismatch: ??x:pred applies the predicate as a guard on each matched element, but ??x’s greedy matching semantics mean that when the constraint fails midway, the match engine backtracks and may succeed with a shorter match rather than failing the entire pattern — a subtlety invisible in the compact pattern notation.
The work log entry read “fixed data filter, 6h.” It names the result and duration. It cannot explain why ??x:isinteger matches [hello world] — Pop-11’s ??x with a type restriction applies the predicate to each candidate element during the greedy match; if the first element fails isinteger, the engine backtracks and tries matching ??x with zero elements, which succeeds because ??x matches zero or more items; the pattern [??x:isinteger] against a list of non-integers binds x to an empty list and succeeds. It cannot explain the difference between ?x:pred and ??x:pred — ?x:pred matches exactly one item satisfying pred; if the current element fails pred, the match fails; there is no backtrack to zero elements because ?x is a single-item variable; ??x:pred matches zero or more items each satisfying pred, so the empty match is always an option. It cannot explain when to use matches versus a recursive procedure — matches is efficient for structural patterns (does the list have this shape?) but the type restriction semantics for rest variables require careful understanding; a recursive procedure using ?x:pred on successive list heads is more explicit about which elements must satisfy the predicate and which positions are structural. The 6 hours of pattern semantics analysis, backtracking diagnosis, and restructuring design are invisible in the diff.
Pop-11 pattern language: ?x, ??x, type restrictions, ==, and matches as a first-class list operation
Pop-11’s pattern language is the core mechanism for structural list matching. A pattern is a list containing literal elements, pattern variables, and type restrictions: [cat ?x dog] matches a three-element list starting with cat, ending with dog, and binding the middle element to x. ?x matches exactly one element without constraint; ?x:pred matches exactly one element satisfying pred (a predicate procedure); ??x matches zero or more consecutive elements; ??x:pred matches zero or more consecutive elements each satisfying pred; == followed by a value matches exactly that value (exact match, bypassing variable binding). The matches operator returns true or false and binds pattern variables as a side effect: list matches [?head ??tail] binds head to the first element and tail to the rest. Unlike Prolog unification, Pop-11 pattern matching is one-directional: the pattern is matched against the data, and variables in the pattern are bound; the data is not a pattern, and variables already bound in the data are compared as values.
The == element matcher is the exact-match primitive for patterns. [?x == stop ??rest] matches a list whose second element is literally stop, binding the first element to x and elements from position three onward to rest. == is necessary for matching literal words that would otherwise be interpreted as pattern elements: without ==, a word in a pattern is a literal match target (Pop-11 matches words in patterns literally), but == followed by a variable name forces the variable’s current value to be matched exactly. matchwhere is the extension of matches with an additional condition: list matchwhere [?x ?y] and x > y matches a two-element list where the first element is greater than the second, binding both; the condition is checked after the structural match and before variable binding is committed. The pattern language also supports !variable for splicing: [!prefix ??rest] splices the list value of prefix into the pattern, matching that segment literally. Pop-11 was designed by Max Clowes and Steven Hardy at the University of Sussex and became the primary language of the Poplog environment. Its retainer work is primarily in maintenance of legacy AI systems written in Pop-11, Poplog-based natural language processing systems, and educational AI platforms at universities that adopted Pop-11 in the 1980s and 1990s. Its closest retainer neighbors are Prolog developer retainers (shared declarative pattern matching heritage) and Lisp developer retainers (shared Poplog environment context), but Pop-11’s unique pattern language semantics, define-with-updater syntax, and procedural-declarative hybrid make the retainer work distinct in pattern variable type restriction reasoning and closure design.
Pop-11 closures, partial application, and define with updater
Pop-11 treats procedures as first-class values. procedure(x) -> result; ... endprocedure is an anonymous procedure. define f(x) -> result; ... enddefine names a procedure in the current dictionary. Closures are created with frozen argument binding: adder(% 5 %) creates a closure of adder with 5 frozen as the first argument (the % operator is the partial application operator in Pop-11). pdpart returns the base procedure of a closure; frozpart returns the list of frozen values. A closure applied to additional arguments appends them to the frozen arguments and calls the base procedure. The partial application model is the primary mechanism for callback and higher-order programming in Pop-11: an event handler is created by partially applying a general handler procedure to the specific context parameters, and the resulting closure is passed as the handler.
Pop-11’s define :updater is one of its most unusual features: every procedure can have an associated updater that inverts the assignment. define front(list) -> result; hd(list) -> result; enddefine; combined with define updater front(val, list); val :: tl(list) -> list; enddefine; means that front(mylist) reads the front, and newval -> front(mylist) assigns through the updater. This enables procedure calls on the left-hand side of assignment: 5 -> subscr(3, mylist) calls the updater of subscr to set the third element of the list to 5. Retainer work involving updaters is typically the design and debugging of stateful abstractions that use this pattern: a property-access procedure with an associated updater, or a view into a data structure where both read and write go through the same named procedure. Pop-11 also supports define :method for object-oriented programming with the FLAVORS mixin system (Poplog’s object extension), and define :class for class declarations. Retainer work in legacy Pop-11 codebases frequently involves understanding whether a particular procedure is a plain procedure, a closure, a method, or a procedure with a custom updater — the Poplog runtime distinguishes these but the source code notation for calling them is identical.
How HourTab tracks Pop-11 developer retainer hours
Pop-11 retainer work carries the invisible-hours problem specific to pattern language semantics: the pattern notation is compact and visually similar to declarative specifications, but the backtracking semantics of rest variables with type restrictions create subtle correctness issues that are invisible until a specific input triggers the wrong match. The ??x:isinteger pattern issue described above is the canonical example: the developer intended a semantic constraint (only match all-integer lists) but wrote a structural constraint that can succeed with an empty binding (match zero elements when the constraint fails). Diagnosing this requires understanding Pop-11’s backtracking semantics at the character level of the pattern, not just the surface-level declaration. Retainer work typically involves pattern audit (every pattern with ??x:pred verified for empty-match behavior), pattern restructuring (replacing rest-variable type restrictions with post-match validation where empty-match is not acceptable), and closure/updater design for stateful abstractions.
HourTab gives Pop-11 developers a public retainer-hours URL they send to clients — typically universities maintaining Pop-11 AI systems developed in the 1980s and 1990s, Poplog environment teams supporting natural language processing applications, and projects extending legacy Pop-11 codebases with new features that must integrate with existing pattern-matching infrastructure. For Pop-11 retainers, each work log entry should name the mechanism (pattern: ??x vs ?x type restriction semantics; closure: partial application with %; updater: define :updater for assignment via procedure call; Poplog: Pop-11/Prolog/Lisp integration), the specific pattern, type restriction, and before/after wrong match count, and the pattern restructuring rationale. Pop-11 retainers are often compared to Prolog developer retainers for the shared pattern and backtracking heritage, but Pop-11’s procedural-declarative hybrid, unique partial application syntax, and define-with-updater model make the retainer work distinct in pattern backtracking analysis, closure composition, and stateful abstraction design. HourTab’s work log makes the pattern semantics audit, type restriction analysis, and closure design visible to clients who would otherwise see only the symptom — wrong matches in a data processing pipeline — and not understand why the fix required understanding that ??x:isinteger in a Pop-11 pattern can succeed with an empty binding on a list that contains no integers at all.
Track Pop-11 developer retainer hours without the status emails
HourTab gives Pop-11 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 pattern language audit log — ??x vs ?x type restriction diagnosis, closure design, updater restructuring — becomes the proof of value that gets the retainer renewed.
FAQ: Pop-11 developer retainers
What does a Pop-11 developer on retainer typically do?
A Pop-11 developer on monthly retainer covers the Pop-11 pattern language (?x for single-item match; ??x for zero-or-more rest match; :predicate type restriction; == for exact element match; matches operator and matchwhere extension; pattern as first-class list), Pop-11 closures and partial application (procedure as first-class value; closure with pdpart/frozpart; partial application with %; recursive procedure definitions; updater procedures with define :updater), and Poplog environment integration (Pop-11 mixed with Prolog or Lisp in Poplog; the Poplog virtual machine; incremental compilation; library loading with uses and lib; Pop-11 I/O with pr and spr).
What Pop-11 work is most commonly underlogged in a retainer?
Pattern matching diagnosis (developer wrote [??x:isinteger]; expected rest-variable matching only integers; ??x:isinteger matches zero or more items each satisfying isinteger; when no integers present, ??x binds empty list and pattern succeeds; wrong matches: 3/list → 0 with post-match forall validation; 6–10 hrs invisible); type restriction design (?x:pred matches exactly one item satisfying pred; ??x:pred matches zero or more; empty-match is always an option for ??x; 4–8 hrs invisible); closure and updater design (Pop-11 define :updater allows assignment via procedure call; partial application with % for callback construction; 4–7 hrs invisible); Poplog multi-language integration (Pop-11 calling Prolog predicates and returning results; Lisp S-expression construction; object-level passing; 5–9 hrs invisible).
What are typical Pop-11 developer retainer rates?
Entry-level Pop-11 developers (1–2 years, basic pattern matching, list processing, Poplog setup) bill at $55–$100/hr. Mid-level Pop-11 AI programmers (2–4 years, pattern language nuances, closure design, Poplog multi-language integration, AI application development) bill at $90–$165/hr. Senior Pop-11 Poplog developers (4–8 years, Poplog virtual machine internals, advanced pattern matching, define :updater design, large-scale AI system maintenance) bill at $130–$245/hr. Monthly retainer ranges: $2,000–$4,500/mo advisory (15–25 hrs), $6,000–$16,000/mo for full Pop-11 AI engineering engagements.
What should a Pop-11 developer retainer agreement include?
A Pop-11 developer retainer agreement should specify: pattern language scope (?x single-item match; ??x zero-or-more rest match; :predicate type restriction per element; == exact match; matches vs matchwhere; pattern as first-class list); closure scope (procedure as first-class value; pdpart/frozpart accessors; partial application with %; recursive procedure definitions); updater scope (define :updater for assignment via procedure; updater invocation; stateful abstraction patterns); Poplog integration scope if applicable (Pop-11 calling Prolog predicates; Lisp value passing; incremental compilation; library loading with uses); and hour logging format (advisory category: pattern, closure, updater, Poplog integration; specific pattern, type restriction, and before/after wrong match count).
How should Pop-11 developer retainer hours be logged?
Log each Pop-11 retainer session with: advisory category (pattern: ?x vs ??x semantics, type restriction application; closure: pdpart/frozpart, partial application design; updater: define :updater, assignment via procedure; Poplog integration: Pop-11/Prolog/Lisp boundary); the specific pattern, type restriction, and before/after wrong match count (original pattern: [??x:isinteger]; expected: match list of only integers; actual: matched any list because ??x can bind empty list; wrong matches: 3/list; fix: post-match forall(x, isinteger) validation; wrong matches: 3/list → 0); and the before/after metric. Include whether fix required changing ??x to ?x, adding post-match validation, restructuring pattern as recursive rule, or combining matches with an explicit list predicate.