Blog › ICP guides
Koka developer on retainer: algebraic effects, fun handler tail-resumptive handlers, resume continuation, effect row types, and Koka effect-type programming on monthly retainer
November 12, 2026 · ~15 min read
A Koka program using an algebraic effect for logging was losing five log entries per computation run. The program declared a log effect with a single operation emit(msg: string): () and wrote a handler to collect all log entries into a list. The handler used a fun clause — Koka’s tail-resumptive handler form — for the emit operation: fun emit(msg) { val (result, acc) = resume(()) ; (result, Cons(msg, acc)) }. The intent was to accumulate all emitted messages into acc by threading the list through each resume call. The failure: the resume call returns the result of the rest of the computation along with the accumulated list from that point forward — but the handler clause’s local acc variable starts empty on the first invocation of the handler and each individual fun clause invocation is a fresh call with fresh local bindings. The Cons(msg, acc) expression appended the current message to the acc that existed in this invocation’s scope, but the acc in the next invocation was again empty because local variables in a fun clause body do not persist across separate emit operations. Each of five emit calls produced a (result, [msg]) pair where acc was the empty list; only the last invocation’s message appeared in the final collected log. Five log entries lost per computation run.
The Koka developer on retainer diagnosed the resume-continuation state-threading failure: in Koka’s algebraic effect system, state must be threaded explicitly through the resume continuation — the handler clause must pass the current accumulated list as the return value that becomes the input to the next continuation step, rather than relying on a local variable that resets each invocation. Restructured the handler using a ctl clause with an explicit state effect for accumulation, or alternatively by threading the accumulated list as part of the return type through each resume(()) call, ensuring each emit operation’s handler clause receives the accumulated list from all previous operations. Log entries lost per computation run: 5 → 0.
The work log entry read “fixed log accumulation handler, 16h.” It names the symptom and duration. It cannot explain to a client why Koka’s fun handler clause does not retain local state across separate operation invocations (a fun clause body is syntactic sugar for a tail-resumptive handler that calls resume exactly once in a non-control-changing context; the Koka compiler optimizes fun handlers into direct function calls because of this guarantee, which means the handler clause body is called fresh for each operation invocation with no shared mutable state between calls — the same property that enables the optimization also eliminates the possibility of persistent local state across invocations), why the fix required auditing every handler in the codebase that attempted to use local variables for accumulation across multiple operation calls (any handler that collected results, counted events, or built up data structures using a local variable rather than explicit state threading or the var built-in mutable state effect was subject to the same reset-per-invocation failure), or why effect row type propagation errors surfaced in three other call sites after the handler was restructured to use ctl (switching from fun to ctl changed the handler’s effect row signature; callers that previously worked because fun handlers are transparent to the caller’s effect row now needed explicit effect row annotations or enclosing handle blocks to satisfy the type checker). The 16 hours of handler clause semantics analysis, resume return type restructuring, effect row propagation audit, and state-threading design are not visible in the diff beyond restructured handler expressions and a few added effect row annotations.
Koka algebraic effects: effect declarations, operations, fun vs ctl handler clauses, and resume continuations
Koka’s algebraic effect system is the language’s primary mechanism for structuring all effectful computation — I/O, state, exceptions, non-determinism, and user-defined control flow. An effect is declared with the effect keyword followed by a block of operation declarations: effect effectName { fun operationName(params): returnType } for tail-resumptive operations or effect effectName { ctl operationName(params): returnType } for general operations. The keyword in the operation declaration (fun vs ctl vs val) is a hint to the handler author about the expected handler clause form, but any operation can be handled by a ctl clause in the handler. Effect operations are called like functions: emit("message") inside a handle block does not return normally — it transfers control to the nearest enclosing handler for the log effect. The handle expression installs a handler: handle { computation() } { return(x) { ... } ; fun emit(msg) { ... } }.
The fun handler clause is Koka’s tail-resumptive form: a fun clause must call resume exactly once, and not in a context that changes the control flow (no wrapping the resume call in a non-tail-resumptive handler of another effect). The Koka compiler can optimize fun handlers into direct function calls — essentially rewriting the effect operation into a plain function call — because the single-resume guarantee makes the operational behavior equivalent. The critical property: a fun clause body is invoked fresh for each operation call. Local variables in a fun clause body are fresh for each invocation. There is no persistent state between separate calls to the same operation within a single handle expression. A fun emit(msg) { val (result, acc) = resume(()) ; (result, Cons(msg, acc)) } clause sees a fresh, empty acc on every separate emit invocation because acc is a local binding in this clause invocation’s scope, not a persistent accumulator across all invocations.
The ctl handler clause is fully general: it receives resume as a first-class continuation value. A ctl clause can call resume zero times (implementing abort or exception semantics), once (normal resumption), or multiple times (implementing non-determinism or backtracking). Because resume is first-class, a ctl clause can store it, pass it to other functions, or call it later from a different context — enabling coroutine patterns. The correct state-accumulation pattern for a log collector: either use the var built-in mutable state (var acc := [], then each fun emit(msg) { acc := Cons(msg, acc); resume(()) } clause mutates the shared var-bound accumulator that persists across all invocations within the handle block), or design the effect to carry the accumulated list as part of its return type and thread it through each resume call explicitly in a ctl clause. The return clause in a handler is called when the handled computation completes with a normal return value; it receives the final value x and its result is the value of the entire handle expression.
Koka effect row types: structural typing, row polymorphism, effect inference, and multi-effect composition
Koka’s type system tracks every effect a function may perform through effect rows embedded in function types. A function type fun f(x: int): <log,exn> int means f takes an int, may perform log and exn effects, and returns an int. A function with effect row <> is total — it performs no effects and always terminates. A function with effect <div> may diverge (non-terminating computation). A function with effect <io> performs arbitrary I/O. Effect rows are part of the function’s type signature, making the implicit control flow of effect operations explicit and checkable by the Koka compiler. A caller of an effectful function either installs a handler with a handle block (satisfying the effect requirement for that context) or must declare the same effect in its own effect row (propagating the effect requirement up to its callers).
Effect row polymorphism allows functions to be polymorphic over the effects of their arguments. A higher-order function fun apply(f: () -> <e|_> int): <e|_> int { f() } works for any effect row e: the effects of f propagate through apply to its caller. This is critical for correct effect-polymorphic library code: a list mapping function that applies a user-supplied function to each element must propagate whatever effects that function performs, or the type checker will reject uses of the mapping function with effectful transformation functions. Effect inference: Koka infers effect rows for most functions automatically. The programmer rarely needs to write explicit effect row annotations except for library interfaces, effect declarations, and cases where the inferred effect row is more permissive than desired. Explicit effect row annotations serve as documentation and as checked constraints on the function’s behavior.
Koka’s built-in effects cover the principal categories of effectful computation. The exn effect covers exceptions: throw and catch are effect operations on the exn effect; a function that throws must have exn in its effect row. The div effect marks computations that may not terminate; the Koka type checker uses termination analysis to infer div when it cannot prove termination. The io effect covers all I/O operations: reading files, writing to stdout, making network calls, and accessing the system clock. The st<h> effect covers mutable state in heap region h; the var keyword desugars to st<h> effects with a locally-scoped heap region. Multi-effect handlers allow a single handle block to handle multiple effects simultaneously by including multiple operation clauses; this is useful when two effects interact and must be handled together. Effect masking with mask<log> hides the log effect from the effect row of a subcomputation — useful for internal diagnostic logging that should not be visible to callers in the public effect interface.
Koka’s design philosophy: effect safety, the Koka research context, and practical algebraic effect patterns
Koka was developed by Daan Leijen at Microsoft Research beginning around 2012, with the goal of demonstrating that algebraic effects can serve as the single primary abstraction for all effectful programming — including I/O, state, exceptions, non-determinism, and user-defined control flow — without the monad-stacking complexity common in Haskell-style purely functional programming. Koka’s type system tracks every effect in function types, making the implicit control flow of effect operations explicit and statically checkable. The Koka compiler proves that effect rows are satisfied: a caller of an effectful function must either be in a context where a handler for that effect is installed, or must declare the effect in its own effect row. No unhandled effect operations can occur at runtime — the type checker verifies at compile time that every operation used inside a handle expression either has a clause in the handler or is present in the handler’s allowed-through effect row.
Practical algebraic effect patterns in Koka cover a wide range of control flow abstractions. The state pattern: using var acc := [] for mutable local variables, which desugars to st<h> effects scoped to the function’s heap region. Local mutable state in Koka is safe because the heap region h is locally scoped and cannot escape the function’s lifetime. The exception pattern: using the exn built-in effect; throw and catch are first-class effects that can be intercepted by user-defined handlers, enabling custom error-handling strategies beyond simple try/catch. The coroutine pattern: using ctl with resume stored as a first-class value; the coroutine can be resumed later from outside the handler, enabling cooperative multitasking and generator patterns where values are yielded one at a time. The non-determinism pattern: ctl choose() { resume(True) + resume(False) } backtracks through all choices and returns the combination of all non-deterministic branches — implementing a complete backtracking search with a handler of fewer than ten lines of Koka code.
Koka’s compilation strategy is evidence-passing: handlers are compiled to functions that pass “evidence” records containing the handler’s operation implementations down the call stack through implicit parameters. Effect operations look up the appropriate handler clause from the evidence record rather than performing dynamic dispatch through a global handler stack. This means effect operation calls in most cases have no more overhead than a record field lookup plus a function call — no stack unwinding, no dynamic handler discovery, no exception-table-based control transfer. The evidence-passing strategy also enables the fun handler optimization: because fun handlers call resume exactly once in a tail-resumptive context, the Koka compiler can replace the evidence-passing machinery with a direct function call, making tail-resumptive handlers as fast as direct function calls in optimized code. Retainer work on Koka program architecture often involves understanding these compilation guarantees: which handler forms trigger the fast path, which effect patterns require evidence-passing overhead, and how to structure multi-effect programs to maximize the cases where the compiler can apply its optimizations.
How HourTab tracks Koka developer retainer hours
Koka retainer work shares the invisible-work problem common to all language engineering retainers, compounded by the fact that Koka’s most common retainer tasks — resume continuation state-threading repair, effect row type propagation audit, fun-to-ctl handler clause restructuring, multi-effect composition design — produce diffs whose surface area is small relative to the diagnostic work. A handler restructuring from a broken fun accumulation clause to a correct ctl clause with var state is a diff with a handful of changed lines; the value is elimination of all state-loss failures from the handler, a correct understanding of Koka’s fun-clause-per-invocation semantics that prevents the same pattern from recurring in the next effect handler the team writes, and a handler design that correctly composes with the rest of the effect row. An effect row type error audit that adds explicit effect row annotations to library interfaces and enclosing handle blocks to four call sites is a diff with a few annotations scattered through the codebase; the value is that all callers now have statically verified effect handling rather than latent unhandled-effect type errors waiting to surface after a refactor.
HourTab gives Koka developers a public retainer-hours URL they send to clients — typically research-adjacent engineering teams exploring algebraic effects for production systems, functional programming consultancies building Koka libraries, and language enthusiasts adopting Koka for domain-specific effect architectures — at the start of an engagement. For Koka retainers, each work log entry should name the mechanism (effect declaration authorship; fun handler clause design and its per-invocation-fresh semantics; ctl handler clause design with first-class resume continuation; resume continuation state threading — state must pass through resume return value, not stored in local handler variable; return clause composition; multi-effect handler block; mask<eff> effect masking; var mutable state with st<h>; exn throw/catch; ndet with ctl choose; coroutine with stored resume; effect row annotation; row polymorphism for higher-order functions), the specific effect and handler names involved, the state-threading failure or effect row type error, and the before/after metric. Koka retainers are often compared to Haskell developer retainers for functional and effect programming comparisons, and to OCaml developer retainers for ML-family functional language comparisons. HourTab’s work log makes the handler clause semantics analysis, resume state-threading restructuring, and effect row propagation audit visible to clients who would otherwise see only the symptom — missing log entries or unexpected state loss — and not understand why the fix required understanding Koka’s algebraic effect handler invocation model and the per-invocation-fresh semantics of fun handler clauses.
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 work log 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 four principal service areas: algebraic effect and handler design (effect declaration authorship; fun vs ctl handler clause selection — tail-resumptive fun for simple transformations, fully general ctl for state accumulation and backtracking; resume continuation threading design; return clause composition; multi-effect handler block authorship; effect masking with mask<eff>); effect row type annotation (effect row inference audit; explicit effect row annotation for library interfaces; row polymorphism design for higher-order functions; multi-effect composition in complex programs); built-in effect patterns (var mutable state with st<h>; exn exception throw/catch; div divergence handling; io I/O isolation; ndet non-determinism with ctl choose; coroutine design with stored resume continuations); and program architecture (effect-based program decomposition; handler installation strategy; effectful vs total function separation; Koka module and type declaration authorship).
What Koka work is most commonly underlogged in a retainer?
Resume continuation state-threading repair (fun handler clause accumulated state in local variable that reset each invocation; only last emit’s message appeared in collected log; 5 log entries lost per run; restructured to thread accumulated list through resume call or use explicit state effect; lost entries: 5/run → 0; 14–22 hrs invisible in handler clause design audit, resume return type analysis, and state-threading restructuring), effect row type error diagnosis (function calling an effectful operation in a context where no handler was installed; Koka type error “unhandled effect log in function f” required adding a handle block around the computation or adding log to f’s declared effect row; 4 type errors in one refactor; 9–16 hrs invisible in effect row propagation analysis across the call chain), and ctl vs fun clause selection mismatch (used fun clause for an operation that needed to accumulate state across multiple calls; switched to ctl with explicit state threading; 8–15 hrs invisible in handler semantics analysis and restructuring).
What are typical Koka developer retainer rates?
Entry-level Koka developers (1–2 years, basic effect declaration, simple fun handlers, built-in var state, exn throw/catch) bill at $60–$105/hr. Mid-level Koka engineers (2–4 years, ctl handlers with resume state threading, effect row type annotation, multi-effect composition, coroutine design, mask<eff> usage) bill at $100–$180/hr. Senior Koka architects (4–8 years, Koka evidence-passing compilation internals, large-scale effect-based program architecture, custom effect combinator libraries, Koka compiler contributions or extensions, type-theoretic effect reasoning) bill at $155–$275/hr. Monthly retainer ranges: $1,600–$4,200/mo advisory (15–25 hrs), $6,000–$15,000/mo for full Koka application engagements.
What should a Koka developer retainer agreement include?
A Koka developer retainer agreement should specify: effect and handler scope (effect declaration authorship; fun vs ctl clause selection; resume continuation threading design; return clause composition; multi-effect handler block; mask<eff> masking); effect row type scope (effect row inference audit; explicit annotation for library interfaces; row polymorphism for higher-order functions; multi-effect composition); built-in effect scope (var/st<h> state; exn exceptions; div divergence; io I/O; ndet non-determinism; coroutine patterns); program architecture scope if applicable (effect-based decomposition; handler installation strategy; total vs effectful function separation); and hour logging format (operation type: handler design, effect row audit, state-threading restructuring; before/after error or state-loss metric; Koka version; whether fix was fun→ctl clause change, resume return type restructuring, state effect addition, or effect row annotation).
How should Koka developer retainer hours be logged?
Log each Koka retainer session with: advisory category (effect declaration authorship; fun handler clause design — tail-resumptive, local state reset each invocation; ctl handler clause design — general, resume is first-class continuation value; resume continuation threading — state must be passed through resume return value, not stored in local handler variable; return clause composition; multi-effect handler block design; mask<eff> effect masking; var mutable state with st<h>; exn throw/catch exception design; ndet non-determinism with ctl choose; coroutine design with stored resume; effect row type annotation; row polymorphism for higher-order functions), the specific effect and handler names involved in the bug (log effect emit(msg) fun handler clause; acc local variable reset each invocation; Cons(msg, acc) always appended to empty list; 5 log entries lost per run; restructured to thread accumulated list through resume or use explicit state effect; lost entries: 5/run → 0), and the before/after observable metric. Include Koka version and target (Koka2 with Node.js backend, WASM target, native C target), and whether fix required fun→ctl clause change, resume return type restructuring, state effect addition, or effect row annotation.