Blog › ICP guides
Koka developer on retainer: algebraic effects, effect handlers, resume, FBIP value types, and Koka functional systems programming on monthly retainer
December 12, 2026 · ~15 min read
A Koka program using algebraic effects for error reporting declared a custom effect effect raise { fun throw(msg : string) : a } and wrote a handler to log errors and return a default value. The developer wrote the handler as handler { throw(msg) -> { log(msg) } } expecting it to catch thrown messages, log them, and continue execution. In Koka’s algebraic effects model, a handler that does not call resume is a final handler: it intercepts the effect operation and terminates the computation at that point, returning from the handler block without continuing the suspended computation. The developer’s handler logged the message but never called resume, so the computation aborted at every throw call site. Missing resume calls: 3 per handler branch. The developer restructured with an explicit resume call providing the default value: handler { throw(msg) -> { log(msg); resume(default) } }. The resume function receives the continuation of the computation from the point where throw was called and resumes it with the provided value as the result of the throw operation. Missing resume calls: 3/handler branch → 0. The Koka developer on retainer diagnosed the resumable-vs-final handler mismatch: in Koka, a handler clause without resume is not an error but a valid final handler pattern — it is the way to implement abort-style exception handling. When the intent was to continue after handling (substituting a default value), resume is the contract that makes the handler resumable rather than final.
The work log entry read “fixed error handling, 8h.” It names the result and duration. It cannot explain why the handler without resume caused the computation to abort — Koka’s effect handlers are delimited continuations: the computation between the with handler installation point and the effect operation site is captured as a continuation; a resumable handler calls resume(value) to continue that computation with value as the result of the operation; a final handler returns from the handler block directly, discarding the captured continuation; the difference is in whether the suspended computation is resumed, not in whether the handler code executes. It cannot explain when to use resumable versus final handlers — resumable handlers model exception recovery, cooperative generators, and async I/O interception (the computation continues from the operation site with a substituted value); final handlers model exceptions (the computation is aborted at the operation site), resource bracketing (the handler runs cleanup before returning), and early exit (the handler returns an early result). It cannot explain Koka’s multi-shot continuations — resume can be called multiple times in a single handler clause, each time continuing the computation from the original operation site with a different value; this models non-determinism (call resume for each choice), iteration (call resume for each element), and logic programming; each resume call produces an independent computation branch. The 8 hours of continuation semantics analysis, resumable/final handler redesign, and multi-shot usage are invisible in the diff.
Koka algebraic effects: effect declaration, fun operations, handler clauses, resume, and effect row types
Koka’s algebraic effects are declared with effect <name> { fun <operation>(...) : <type>; ... }. An effect declares one or more operations, each with a name, parameter types, and a return type. The return type of an operation is the type that resume must provide — it is the value that the caller of the operation receives when the handler resumes the computation. A function that calls an effect operation is typed with that effect in its effect row: fun f() : <raise, io> int { ... } has effects raise and io. A handler eliminates an effect from the row: installing a handler for raise around a computation with row <raise, io> produces a computation with row <io>. Koka’s type system infers effect rows automatically; the programmer rarely needs to write them explicitly, but understanding them is essential for handler placement: a handler must be installed before the effect operations it handles are called, and the handler’s scope determines which calls it intercepts.
The with statement in Koka is the primary mechanism for lexically scoping effect installation. with handler { op(x) -> resume(f(x)) }; body installs the handler for the duration of body. with return(x) { transform(x) }; body applies a transformation to the return value of body. with val x = expr; body is a scoped binding. The with statement is syntactic sugar for mask and handler composition that expresses the common pattern of installing an effect handler in the current scope. Effect handlers can also have a return clause: handler { return(x) { wrap(x) }; op(args) -> resume(default) } transforms the final return value of the handled computation via wrap, enabling post-computation processing like collecting results, finalizing resources, or wrapping return values in a container. Koka was designed by Daan Leijen at Microsoft Research. Its effect system is the first industrial-strength language with first-class algebraic effects and effect rows in the type system. Its retainer work is primarily in research software needing principled effect management, high-performance functional systems where FBIP provides zero-cost in-place mutation, and teams migrating from monadic I/O designs to algebraic effects. Its closest retainer neighbors are Haskell developer retainers (shared functional systems context) and OCaml developer retainers (OCaml 5 effect system), but Koka’s effect row type inference, FBIP ownership model, and resumable continuation semantics make the retainer work distinct.
Koka FBIP: unique ownership, in-place mutation, reuse analysis, and functional performance without allocation
Koka’s FBIP (functional but in-place) is the mechanism that makes purely functional programs execute with the performance of imperative in-place mutation. The key insight: when a data structure has a unique reference (exactly one live pointer to it), modifying it produces a new value that can reuse the original allocation — there is no need to copy because no other code can observe the original value. Koka’s reuse analysis detects when a value is consumed with a unique reference and the resulting constructed value has the same shape, generating an in-place update in the compiled C code without allocation. fun increment(xs : list<int>) : list<int> { xs.map(fn(x) { x + 1 }) } looks like it allocates a new list on every call; with FBIP, when xs is unique at the call site (no other references exist), the map operation reuses the list spine in-place and updates each cell. The generated C code is equivalent to an imperative loop updating the cells directly.
The retainer work involving FBIP is typically the analysis and restructuring of functions that inadvertently duplicate a unique reference, breaking the uniqueness invariant and causing copies where in-place updates were expected. Common patterns: a function that stores a value in two places (a data structure and a local variable) makes both references non-unique; a closure that captures a value and is passed to two callees makes the captured value non-unique at each call. Koka’s drop and reuse annotations allow manual hints to the reuse analysis: reuse xs in { ... } asserts that xs will be consumed and its allocation reused; if the assertion is wrong (xs is not consumed), the compiler emits a warning. The performance difference between the FBIP path (zero allocations) and the non-FBIP path (one allocation per constructor per recursive call) can be significant for list and tree processing in tight loops. Profiling Koka programs for allocation pressure and restructuring to preserve uniqueness is the core FBIP retainer task. The analysis is invisible in the diff because the restructuring changes variable usage patterns (one fewer binding, one fewer alias) rather than computation logic.
How HourTab tracks Koka developer retainer hours
Koka retainer work carries the invisible-hours problem specific to algebraic effects: the computation may appear structurally correct — effect declared, handler installed, operations called — until the resumable/final handler distinction causes silent computation termination at every operation site. The missing resume pattern described above is the single most common correctness issue in Koka programs written by developers familiar with exception handling from other languages: they model throw as raise-and-recover (exception style, resumable expected) but write a final handler (no resume), and the computation terminates silently without error. Diagnosing this requires understanding that Koka does not distinguish resumable from final handlers at the effect declaration level — both are written with the same handler syntax; the distinction is solely in whether resume is called. A retainer engagement typically involves handler audit (every handler clause verified for correct resumable/final intent), effect row audit (every function’s effect row verified against its handler placement), and FBIP audit (hot paths analyzed for uniqueness preservation).
HourTab gives Koka developers a public retainer-hours URL they send to clients — typically research engineering teams building effect-based systems, high-performance functional systems teams using FBIP to eliminate allocation in tight loops, and projects migrating from monadic Haskell or OCaml effect systems to Koka’s native algebraic effects. For Koka retainers, each work log entry should name the mechanism (handler: missing resume, resumable vs final handler redesign; FBIP: unique ownership, reuse analysis, allocation elimination; effect row: type annotation, handler placement, row composition; with-scoping: lexical effect installation), the specific effect, handler branch, and before/after computation abort or allocation count, and the handler design rationale. Koka retainers are often compared to Haskell developer retainers for the shared functional systems programming context, but Koka’s first-class algebraic effects with resumable continuations, effect row type system, and FBIP ownership model make the retainer work distinct in handler semantics reasoning, continuation design, and allocation analysis. HourTab’s work log makes the handler design, effect row analysis, and FBIP restructuring visible to clients who would otherwise see only the symptom — computations that silently terminate before completion — and not understand why the fix required understanding that a Koka handler without resume is not an incomplete handler but a complete final handler that aborts the computation by design, and why the difference between handler { throw(msg) -> log(msg) } and handler { throw(msg) -> { log(msg); resume(default) } } is the difference between exception-style abort and exception-recovery-style continuation.
Track Koka developer retainer hours without the status emails
HourTab gives Koka 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 algebraic effects audit log — resumable vs final handler diagnosis, FBIP reuse analysis, effect row composition — becomes the proof of value that gets the retainer renewed.
See HourTab pricing →FAQ: Koka developer retainers
What does a Koka developer on retainer typically do?
A Koka developer on monthly retainer covers algebraic effects (effect declaration with effect keyword; fun operation inside effect; handler { op(...) -> resume(value) } for resumable handlers; final handler variant that does not resume; effect row type inference — <e | e1, e2>; with statement for lexically scoped effect installation), Koka FBIP value types (functional but in-place mutation; unique ownership for in-place update without allocation; reuse analysis that detects when a value can be updated in place; drop and reuse annotations for manual optimization; reference counting elimination via FBIP), and Koka expression evaluation (tail call optimization; named function returns; match expression on algebraic data types; value versus reference semantics).
What Koka work is most commonly underlogged in a retainer?
Effect handler resume diagnosis (developer declared effect raise { fun throw(msg : string) : a }; handler { throw(msg) -> log(msg) } without resume; handler aborts computation at every throw site; missing resume calls: 3/handler branch → 0 with { throw(msg) -> { log(msg); resume(default) } }; 6–10 hrs invisible); resumable vs final handler design (resumable handler must call resume(value) to continue from operation site; final handler aborts without resumption; wrong handler type causes silent computation termination; 4–8 hrs invisible); FBIP ownership design (unique values updated in-place without allocation; non-unique values require copy; reuse analysis detects unique usage; unnecessarily copied values due to non-unique usage; 5–9 hrs invisible); effect row inference (function type inferred with wrong effect row causing handler placement errors; type annotation required to constrain row; 4–7 hrs invisible).
What are typical Koka developer retainer rates?
Entry-level Koka developers (1–2 years, algebraic effect basics, basic handler syntax, Koka standard library) bill at $65–$115/hr. Mid-level Koka algebraic effects programmers (2–4 years, resumable vs final handlers, FBIP value type design, effect row type inference, with-statement scoping) bill at $110–$190/hr. Senior Koka functional systems developers (4–8 years, FBIP reuse analysis, complex effect row composition, multi-shot continuation design, performance-critical Koka systems programming) bill at $160–$280/hr. Monthly retainer ranges: $2,500–$5,000/mo advisory (15–25 hrs), $7,000–$19,000/mo for full Koka systems engineering engagements.
What should a Koka developer retainer agreement include?
A Koka developer retainer agreement should specify: effect scope (effect declaration; fun operation; handler { op(...) -> resume(value) }; resumable vs final handlers; effect row type annotation; with statement for scoped installation); FBIP scope (unique ownership; in-place mutation without allocation; reuse analysis; drop/reuse annotations; performance impact of non-unique values); handler design scope (resumable continuation — resume(value) to continue from operation site; final handler — abort at operation site; multi-shot continuation — calling resume multiple times; handler return clause for post-computation transformation); effect row inference scope (effect row composition; type annotation to constrain inferred row; handler placement relative to effect row); and hour logging format (advisory category: handler, FBIP, effect row, with-scoping; specific effect, handler branch, and before/after computation abort or allocation count).
How should Koka developer retainer hours be logged?
Log each Koka retainer session with: advisory category (handler: missing resume, resumable vs final handler type; FBIP: unique ownership, reuse analysis, in-place mutation; effect row: type inference, row composition, annotation; with-scoping: lexical effect installation, handler scope); the specific effect, handler branch, and before/after computation abort or allocation count (effect: raise; operation: throw(msg : string) : a; handler original: { throw(msg) -> log(msg) } without resume; result: computation aborted at every throw site; aborts: 3/handler branch; fix: { throw(msg) -> { log(msg); resume(default) } }; aborts: 3/branch → 0); and the before/after metric. Include whether fix required adding resume call, changing to final handler and restructuring caller, redesigning effect as a value return, or restructuring with multi-shot resume for iteration.