Blog › ICP guides
Eff developer on retainer: algebraic effects with handlers, uncaught-effect diagnosis, continuation-based State and Logging effects, and OCaml-backend programs on monthly retainer
September 26, 2026 · ~15 min read
An Eff stateful computation pipeline was being built using algebraic effects. The developer defined a State effect with get and set operations to thread state through the computation, and composed it with a Logging effect that had a log operation to record events during processing. The developer wrote a handler for State and wrapped the computation with it using with stateHandler handle computation. The Logging effect was defined with its operations, but no handler for Logging was installed — the developer expected the log calls to either silently no-op or be handled by a default. In Eff, every effect operation that is called in the dynamic extent of a computation must have a corresponding handler installed somewhere on the handler stack that covers that operation; if Logging.log is called and there is no Logging handler active, Eff raises a runtime uncaught-effect error. Uncaught effect errors: 2 per computation run. The Eff developer on retainer identified the missing handler and restructured the composition: a Logging handler was defined and installed to wrap the computation along with the State handler, so all log calls in the computation’s dynamic extent are covered. Uncaught effect errors: 2 → 0.
The work log entry read “fixed missing Logging handler in pipeline computation, 6h.” It names the result and duration. It cannot explain why there is no default behavior for unhandled effects in Eff — Eff’s design principle is that effects are explicit and handlers are the mechanism through which effects are given meaning; an unhandled effect is not a no-op or a default action, it is an error, because the language cannot know what the developer intended the effect to mean without a handler that specifies that meaning; a Logging.log operation might mean write to stdout, write to a file, buffer in a list, or do nothing — the handler is what determines which interpretation applies in a given context; having no handler means having no interpretation, which Eff correctly identifies as an error. It cannot explain the continuation semantics in the handler body — when a handler handles an effect operation, it receives the continuation k, which is a first-class function representing “the rest of the computation after this operation”; calling k x resumes the computation with x as the result of the operation; a handler that does not call k implements abort-style control flow (the computation does not continue after the operation — this is how the Fail effect implements exceptions); a handler that calls k twice or more implements non-determinism (the computation continues in multiple branches — this is how the NonDet effect implements backtracking). It cannot explain the effect interaction semantics of handler nesting order — if the State handler is installed outside the Logging handler, state is threaded across all log calls; if the Logging handler is installed outside the State handler, the logged values include the pre-handler state that is then transformed by the State handler, and the interaction can produce different results depending on which handler sees the operation first. The 6 hours of missing handler detection, handler composition design, continuation semantics analysis, and effect interaction reasoning are invisible in the diff.
Eff algebraic effects: effect declarations, handler bodies, continuation resumption, and the value return clause
Effects in Eff are declared with the effect keyword: effect Logging: operation log : string -> unit declares a Logging effect with one operation log that takes a string and returns unit. Handlers for effects are expressed as values: let loggingHandler = handler { val return x = x; operation log msg k = print_endline msg; k () }. The handler has two clauses: the val return x clause transforms the final return value of the handled computation (here, the identity); the operation log msg k clause handles each log call by printing the message and then calling the continuation k () to resume the computation with unit as the result of the log call. The handler is installed with with loggingHandler handle computation; the computation expression runs in a context where log operations are dispatched to loggingHandler.
The continuation k is the key mechanism that distinguishes algebraic effect handlers from exception handlers. In a conventional exception handler, catching an exception does not resume the computation that threw it; the exception handler replaces the remaining computation with its own body. In Eff, the handler body receives the continuation of the operation caller: after the log call, the computation expects to receive a unit value and continue; k () provides that value and the computation continues from the point of the log call. This makes State implementable as an effect: operation get () k = (fun s -> let (a, s') = k s in (a, s')) passes the current state to the continuation and threads the resulting state; operation set s' k = (fun s -> let (a, s'') = k () s' in (a, s'')) updates the state and resumes. Eff was designed by Andrej Bauer and Matija Pretnar at the University of Ljubljana as one of the first practical programming languages with algebraic effects and handlers. Its closest retainer neighbors are Effekt developer retainers (Effekt uses lexical handler scoping and second-class capabilities; Eff uses dynamic-extent handler dispatch) and Koka developer retainers (Koka has row-polymorphic effects and a different effect type system), but Eff’s continuation-based handler model, its built-in State, IO, Fail, and NonDet effects, and its OCaml compilation backend make the retainer work distinct in continuation design, effect composition analysis, and OCaml interop.
Eff built-in effects: State, IO, Fail, NonDet, and handler composition
Eff’s standard library provides several commonly used effects. The State effect from the stdlib provides get() (returns the current state value) and set(v) (updates the state value and returns unit) operations; the state handler threads a state value through the computation using the continuation-based approach above. The IO effect covers console input/output and file operations. The Fail effect implements exception-style control flow: the fail(msg) operation raises a failure; the Fail handler can either catch the failure and return an option value or propagate it further. The NonDet effect implements non-determinism: the decide() operation returns a boolean; the handler calls the continuation twice (once with true and once with false) and collects all results; this is the mechanism for implementing backtracking search in Eff by expressing it as an effect.
Handler composition in Eff is sequential: to use both State and Logging, the computation is wrapped with both handlers: with stateHandler handle (with loggingHandler handle computation). The nesting order determines the interaction semantics: the outer handler sees the result of the inner handler’s transformation. When State is outer and Logging is inner, the State handler processes the pair of (a, s) results that the Logging handler produces; when Logging is outer and State is inner, the Logging handler processes the log-augmented results that the State handler produces. The choice of nesting order is a semantic design decision that affects what the program computes, not just a syntactic convention. Eff programs are run with eff script.eff for interpreted execution; eff --native script.eff compiles to OCaml and applies the OCaml native-code compiler for performance; eff --js script.eff produces JavaScript for browser or Node.js execution. The OCaml backend makes Eff programs compatible with OCaml libraries: Eff code compiled with --native can call OCaml functions through the standard OCaml FFI mechanism.
How HourTab tracks Eff developer retainer hours
Eff retainer work carries the invisible-hours problem specific to effect handler composition systems: the question “which effects are used by this computation and do all of them have handlers installed?” requires a complete trace of all effect operations called in the dynamic extent of the computation, which is a control-flow analysis problem, not a syntactic one. The missing handler error described above — where Logging.log was called but no Logging handler was installed — appears simple in retrospect: add a handler. But the work before that addition was identifying that the computation used Logging at all (tracing all call sites of log through helper functions that compose the pipeline), understanding why no handler was installed (the developer expected a default no-op but Eff has no such default), and determining whether the correct fix was to add a real logging handler (that writes to stdout or a file), a no-op handler (that discards log calls), or to restructure the computation to not use the Logging effect in paths that are not wrapped by a handler. Each of those choices has different semantics and different implications for the program’s behavior in production versus testing contexts. That analysis — five to nine hours of effect usage tracing, handler semantics review, and composition restructuring — is invisible in a diff that shows a handler installation added in one location.
HourTab gives Eff developers a public retainer-hours URL they send to clients — typically programming-languages researchers using Eff to prototype algebraic effect systems and their semantics, backend engineers exploring algebraic effects as a replacement for monadic I/O abstractions, and teams using Eff to implement non-deterministic algorithms where backtracking is expressed cleanly as an effect. For Eff retainers, each work log entry should name the mechanism (handler: effect name, whether handler was missing, uncaught error count before and after; continuation: operation name, whether k was called zero, one, or multiple times, what value was passed to k; effect interaction: handler nesting order, which effect was outer, how the nesting order affected the computation result; OCaml interop: which OCaml function was called, FFI pattern used). Eff retainers are often compared to Effekt developer retainers for the shared algebraic-effects foundation, but Eff’s dynamic-extent handler dispatch (where any handler on the dynamic call stack can handle an operation, unlike Effekt’s lexical scoping), its first-class continuation model (where the continuation k is a function value that can be called zero, one, or multiple times to implement different control-flow patterns), and its built-in State, IO, Fail, and NonDet effects from the standard library (which Effekt does not provide) make the retainer work distinct in handler composition auditing, continuation design, and dynamic-extent effect tracking. HourTab’s work log makes the missing handler detection, handler composition restructuring, and continuation semantics analysis visible to clients who would otherwise see only the symptom — an uncaught effect error at a call site they thought was handled — and not understand why the fix required tracing all effect operations in the computation’s dynamic extent, identifying which ones lacked handlers, determining what the correct handler semantics should be, and choosing a handler nesting order that produced the intended interaction between the State effect’s state threading and the Logging effect’s event recording.
Track Eff developer retainer hours without the status emails
HourTab gives Eff 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 — missing handler detection, continuation design, effect composition restructuring — becomes the proof of value that gets the retainer renewed.
See HourTab pricing →FAQ: Eff developer retainers
What does an Eff developer on retainer typically do?
An Eff developer on monthly retainer covers algebraic effect system design (effect StateName: operation op : ParamType -> RetType declarations; handler { val return x = ...; operation op x k = ... } where k is the first-class continuation; k x resumes computation with x; value return x transforms final result; with handler handle computation for dynamic-extent dispatch; effect composition by handler sequencing), Eff built-in effects (State with get()/set(v); IO for input/output; Fail for exception-style control flow; NonDet for non-determinism and backtracking; handler nesting order semantics), and Eff type system and compilation (int, float, bool, string, unit; product types a * b; sum types a + b; list; function a -> b; computation type a ! {eff1, eff2}; eff script.eff interpreted; --native OCaml native code; --js JavaScript; OCaml interop).
What Eff work is most commonly underlogged in a retainer?
Missing handler diagnosis (State handler installed, Logging handler missing; Logging.log calls in dynamic extent with no Logging handler; uncaught effect errors: 2/computation run; installed Logging handler wrapping computation; errors: 2/run → 0; 5–9 hrs invisible); continuation design (handler body receives k; k x resumes; not calling k: abort-style; calling k multiple times: non-determinism; val return x transforms final result; 5–8 hrs invisible); effect interaction analysis (handler nesting order semantics; State outer vs inner relative to Logging; interaction producing different results; 4–7 hrs invisible); OCaml interop design (eff --native OCaml compilation; OCaml library calls; FFI patterns; 3–6 hrs invisible).
What are typical Eff developer retainer rates?
Entry-level Eff developers (1–2 years, basic effect declarations, handler installation, continuation resumption) bill at $60–$110/hr. Mid-level Eff programmers (2–4 years, missing handler diagnosis, continuation design, effect interaction analysis, built-in effect composition) bill at $95–$175/hr. Senior Eff algebraic effects developers (4–8 years, complex handler architectures, multi-shot continuation patterns, non-determinism and backtracking designs, large-scale effect composition) bill at $140–$255/hr. Monthly retainer ranges: $2,000–$4,000/mo advisory (15–25 hrs), $5,500–$13,500/mo for full Eff effect system engineering.
What should an Eff developer retainer agreement include?
An Eff developer retainer agreement should specify: effect system design scope (effect declaration; handler { val return x; operation op x k }; k x continuation resumption; with handler handle composition; dynamic-extent dispatch; handler nesting order semantics); built-in effects scope (State: get/set; IO: input/output; Fail: exception-style; NonDet: non-determinism; which built-in effects are used and how composed); type system scope (int/float/bool/string/unit; product and sum types; list; computation type a ! {effs}); compilation scope (eff interpreted; --native OCaml; --js JavaScript; OCaml interop; specific OCaml library being called); and hour logging format (handler: effect name, whether missing, error count before/after; continuation: operation, k call count, value passed; specific effect and operation names).
How should Eff developer retainer hours be logged?
Log each Eff retainer session with: handler category (State handler installed; Logging handler missing; Logging.log calls in dynamic extent with no Logging handler active; uncaught effect errors: 2/computation run; installed Logging handler: with loggingHandler handle computation; errors: 2/run → 0; specific effect name, operation name, uncaught error count); continuation category (handler body: operation log msg k = print_endline msg; k (); k x: resumes computation with x as operation result; not calling k: abort, computation does not continue; calling k twice: non-determinism, two branches; val return x = ...: transforms final result of entire handled computation; specific operation and k call count); effect interaction category (handler nesting order: with stateHandler handle (with loggingHandler handle computation) vs reversed; outer handler sees inner handler’s result; state threading across log calls vs resetting per log; specific effects and how nesting order changed the result); OCaml interop category (eff --native: compiles via OCaml native compiler; OCaml function accessible from Eff; FFI declaration; specific OCaml library and function); and before/after uncaught effect error count per computation run.