Blog › ICP guides

Idris developer on retainer: dependent types, Vect n a, totality checking, proof terms, Curry-Howard, and dependently typed programming on monthly retainer

November 25, 2026 · ~16 min read

An Idris program implementing a dependent vector library was producing one type error per refactor of a split call site. The program used Idris’s length-indexed vector type Vect n a, where n is a Nat value encoded in the type itself: a Vect 3 String is a vector of exactly three strings, and the length is enforced at compile time. The library included an append function typed append : Vect m a -> Vect n a -> Vect (m + n) a, where the output length m + n is computed in the type from the input lengths — the compiler verifies that the result has exactly the right length. The team needed a split function that inverted append: given a Vect (m + n) a, return a pair (Vect m a, Vect n a). Because the split point m is not recoverable from the vector’s elements at the type level, split required a runtime Nat argument for m: split : (m : Nat) -> Vect (m + n) a -> (Vect m a, Vect n a). This argument tells the function how many elements to take from the front of the combined vector. At one call site, the developer was working in a context that had both m : Nat and n : Nat in scope. The code called split n combined, intending to pass the split point as a Nat. But the correct variable in this context was m, not n: split’s first argument is the number of elements to take from the front, which is m. Idris’s type checker immediately rejected the call: the Nat value n had type Nat but it did not unify with the expected type for the split point in the dependent type context, because the return type of split n combined would be (Vect n a, Vect m a), which did not match the expected (Vect m a, Vect n a) at the use site. The Idris developer on retainer diagnosed the dependent type variable scope mismatch: the split point argument must be the length of the first output vector, which in this context is m, not n. Restructured the call to split m combined. Type errors per call site refactor: 1 → 0.

The same retainer engagement also addressed totality failures in functions over Vect. A helper function was written to process vectors that always have at least one element, typed to accept Vect (S n) a (a vector whose length is the successor of some n, meaning at least one element). The function’s pattern match handled the (x :: xs) case correctly. The Idris totality checker rejected the function with three totality failures per function written this way: Idris’s total annotation requires that every pattern is covered and every recursive call decreases structurally. A function typed Vect (S n) a is guaranteed by the type to receive a non-empty vector, but the totality checker requires all logical patterns in the match to be covered — including the [] (empty vector) case. A function typed over Vect (S n) a is a dependent match over the Vect constructor, and the compiler requires an explicit Nil case, even though the type index guarantees it can never be reached. Adding the [] base case with a proof-level expression or an absurd call eliminated the totality failures. Totality failures per function: 3 → 0.

The work log entry read “fixed dependent type and totality errors in split library, 11h.” It names the symptom and duration. It cannot explain why passing n where m was expected produced a type error in Idris but might have produced no error in a non-dependent type system — in a non-dependent language, both m and n have type Int or Nat; the compiler accepts either; the wrong value produces a runtime wrong split, discoverable only by testing. In Idris, the dependent type encodes the relationship between the split point and the output vector lengths at the type level; passing the wrong variable violates that relationship, and the type checker catches it before the program runs. It cannot explain why the totality checker requires the [] case on a function typed Vect (S n) a — the type index guarantees the input is non-empty, but Idris’s totality checking is a syntactic analysis over the match patterns, not a semantic analysis of type index reachability; the syntactic check requires every constructor to be covered. It cannot explain the retainer’s selection of absurd over an explicit impossible branch — absurd takes a value of type Void and returns any type, making it the canonical pattern for unreachable cases when a proof of impossibility is available. The 11 hours of dependent type variable scope analysis, totality failure diagnosis, and absurd-branch pattern selection are invisible in the diff.

Idris dependent types: Vect n a, pi types, type-level Nat computation, and implicit arguments

Idris’s dependent type system allows types to depend on runtime values. The canonical example is Vect n a, a vector parameterized by both its element type a and its length n : Nat. The Nat index is part of the type: Vect 3 String and Vect 4 String are different types, and the compiler ensures that operations on vectors produce results with the correct length types. The append function’s type Vect m a -> Vect n a -> Vect (m + n) a is a dependent function type where the return type contains a computation (m + n) performed on the argument types. The compiler verifies that every call site produces a result of the declared dependent type.

Dependent function types in Idris are written with explicit variable binders: (n : Nat) -> Vect n a -> a is a function that takes a Nat value n and a Vect n a vector and returns an element of type a. The binder (n : Nat) introduces n as a named value that can appear in subsequent types; this is the dependent pi type. Idris supports implicit arguments with curly braces: {n : Nat} -> Vect n a -> a allows the compiler to infer n from the type of the Vect argument at the call site, avoiding the need to pass n explicitly. The retainer pattern: when debugging dependent type errors, use :t in the Idris REPL to inspect the types of expressions in context; the type of each variable in scope is displayed, which reveals when the wrong variable is being passed to a dependent argument.

Hole-driven development is the Idris retainer pattern for constructing functions whose types are complex: write the function signature, replace the body with a hole ?hole_name, and use :t ?hole_name in the REPL to inspect what type the hole must have given the current context. Idris displays the type of the hole and all bindings in scope, including their precise dependent types. For split, the hole inspection reveals which Nat variable in scope has the type that unifies with the split-point argument: the developer can see m : Nat and n : Nat with their roles in the current dependent type context, identifying which must be passed to split’s first argument. This technique eliminates guessing when multiple Nat values are in scope.

Idris totality checking: total, covering, partial, structural recursion, and absurd branches

Idris’s totality checker verifies two properties: coverage (every case in a pattern match is handled) and termination (every recursive call decreases structurally). A function annotated %total must satisfy both. A function annotated %covering only needs coverage, not termination. A function annotated %partial has totality checking disabled; this is appropriate for functions known to be partial (for example, a parser that may fail on arbitrary input) but should be used sparingly. The totality checker enforces Idris’s ambition as a language for verified programming: a total function is a mathematical proof that the computation always terminates and always produces a value; non-total functions introduce logical inconsistency into the proof context.

The syntactic coverage requirement means that even functions whose types guarantee non-empty input must explicitly handle all constructors. A function typed Vect (S n) a -> result is guaranteed by the type to receive a non-empty vector: the type index S n (the successor of n) is the successor-constructor form of Nat, which means the length is at least one. But Idris’s coverage checker is syntactic: it requires that the Nil constructor of Vect be addressed in the match. The idiomatic pattern is to provide the Nil case using absurd, which takes a proof of Void (the uninhabited type) and returns any type. For a Nil branch in a function typed Vect (S n) a, the Nil constructor has length Z (zero), but the type requires length S n; the two cannot unify. Idris can automatically prove this impossibility via the Refl tactic or the absurd function, making the unreachable branch explicit and satisfying the coverage checker.

Structural recursion termination is verified by Idris by checking that each recursive call is on a structurally smaller argument: a recursive call on the tail of a list is smaller than the original list; a recursive call on n in a function over Vect (S n) a passes n (one smaller) to the recursive invocation. Non-structural recursion — recursion that does not obviously decrease a structural argument — requires a well-founded recursion proof or a termination measure annotation. The retainer pattern: when the totality checker rejects a recursive function, identify the decreasing argument; if it is not structurally decreasing, restructure to pass a structural sub-part or provide an explicit termination measure.

Idris proof terms: Curry-Howard, Refl, rewrite, Void, and propositional equality

Idris embodies the Curry-Howard correspondence: propositions are types and proofs are programs. A type like n = m (propositional equality between two Nat values) is a proposition; a value of that type is a proof that n and m are definitionally equal. The only constructor for propositional equality is Refl : a = a, which proves that any value is equal to itself. To prove that two computed expressions are equal — for example, that m + (n + 0) = m + n — the developer constructs a proof term by induction, applying the rewrite tactic to substitute one expression for an equal one in the goal type.

The rewrite tactic is the mechanism for using equality proofs to change the type of a goal. If the current goal has type Vect (m + (n + 0)) a and a proof prf : n + 0 = n is in scope, then rewrite prf transforms the goal to Vect (m + n) a. This is the bridge between type-level arithmetic and dependent type indices: when the type checker requires a value of type Vect k a and the available expression has type Vect j a where j and k are definitionally equal but not syntactically identical, a rewrite with the equality proof resolves the mismatch. The retainer pattern: rewrite failures arise when the proof direction is reversed (rewrite sym prf uses the symmetric form) or when the equality involves implicit arguments that must be made explicit. Idris was created by Edwin Brady, initially released around 2011, with Idris 2 being a full redesign released around 2020 using quantitative type theory. Its closest relatives in the retainer ecosystem are Haskell for the functional programming foundation and Coq/Agda for the dependent type and proof verification workflow, but Idris’s focus on practical systems programming alongside theorem proving makes the retainer work distinct in dependent type engineering, totality discipline, and proof term construction for production code.

How HourTab tracks Idris developer retainer hours

Idris retainer work carries the invisible-work problem of all type-system-heavy languages, amplified by the gap between dependent types’ expressiveness and the debugging intuition required to use them. Teams adopting Idris for verified embedded systems or protocol libraries frequently encounter the dependent variable scope problem the first time a function with two Nat indices in scope is refactored: the type error Idris reports is precise — it names the expected and actual types — but reading the error requires understanding the dependent type context, the variable bindings in scope, and how passing the wrong Nat variable propagates into the return type index. The one type error per call site refactor described above is one instance of this diagnosis; the retainer work is the systematic inspection of the dependent type context, identification of the correct variable, and verification that the fix satisfies the return type constraint across all use sites.

HourTab gives Idris developers a public retainer-hours URL they send to clients — typically research teams building verified systems software, organizations embedding Idris for protocol correctness proofs, and teams using Idris’s total functions as machine-checked guarantees for safety-critical paths. For Idris retainers, each work log entry should name the mechanism (wrong dependent type variable in split call site; missing base case for totality; structural recursion termination redesign; covering vs total annotation selection; Refl propositional equality proof; rewrite tactic equality substitution; sym for reversed equality direction; Void contradiction with absurd; Not a = a -> Void negation; case analysis proof by induction; implicit argument explicit instantiation; hole-driven development), the specific type names, dependent indices, Nat variables, and pattern cases involved in the bug, and the before/after metric. Idris retainers are often compared to Haskell developer retainers for the shared functional programming background, but Idris’s dependent type indices, totality checker, and proof term construction make the retainer work distinct in type-level variable scope debugging and structural recursion engineering. HourTab’s work log makes the dependent type context analysis, variable scope inspection, and totality coverage fix visible to clients who would otherwise see only the symptom — one type error per call site refactor — and not understand why the fix required understanding how dependent type indices encode the relationship between Nat variables and why passing the wrong one violates the dependent return type contract.

Track Idris developer retainer hours without the status emails

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

What does an Idris developer on retainer typically do?

An Idris developer on monthly retainer covers four service areas: dependent type design and debugging (Vect n a length-indexed vector engineering; dependent pi type (x : A) -> B x; type-level Nat computation; implicit argument inference; hole-driven development); totality checker compliance (missing pattern case identification; structural recursion discipline; covering vs partial vs total annotation; absurd for unreachable cases; %total enforcement); proof term construction (Refl propositional equality; rewrite tactic; case analysis and induction; Void contradiction; Not a = a -> Void); Idris type-level programming (data type promotion; interface typeclass design; elaborator reflection in Idris 2).

What Idris work is most commonly underlogged in a retainer?

Wrong dependent type variable in call sites (split required Nat m for split point; developer passed n where m expected; return type mismatch (Vect n a, Vect m a) vs expected (Vect m a, Vect n a); 1 type error per call site refactor; correct variable passed; errors: 1/call site → 0; 6–12 hrs invisible); totality checker non-total function repair (Vect (S n) a function lacked Nil case; totality checker rejected; 3 totality failures per function; absurd base case added; failures: 3/function → 0; 8–15 hrs invisible); and propositional equality proof construction (rewrite tactic required for type index equality substitution; 10–18 hrs invisible in Curry-Howard proof engineering).

What are typical Idris developer retainer rates?

Entry-level Idris developers (1–2 years, basic data types, simple dependent functions, Idris standard library) bill at $75–$130/hr. Mid-level Idris engineers (2–4 years, Vect n a dependent vectors, totality checker compliance, interface design, proof construction, Idris 2 elaborator reflection) bill at $125–$210/hr. Senior Idris architects (4–8 years, complex type-level computation, full Curry-Howard proof engineering, Idris metaprogramming, interop with Scheme or JavaScript backends, large-scale verified system design) bill at $180–$310/hr. Monthly retainer ranges: $2,200–$5,200/mo advisory (15–25 hrs), $7,500–$20,000/mo for full Idris systems development engagements.

What should an Idris developer retainer agreement include?

An Idris developer retainer agreement should specify: dependent type scope (Vect n a length-indexed vector design; dependent pi type; type-level Nat computation; implicit argument inference; hole-driven development); totality checker scope (total vs partial vs covering annotation; structural recursion; missing pattern coverage; absurd for unreachable cases; %total enforcement); proof term scope (Refl and propositional equality; rewrite tactic; case analysis and induction; Void contradiction; Not a = a -> Void); interface scope (interface typeclass design; auto-implicit arguments); Idris metaprogramming scope (elaborator reflection in Idris 2); and hour logging format (advisory category, before/after type error or totality failure metric, Idris version, whether fix required correct variable, base case, rewrite, or interface redesign).

How should Idris developer retainer hours be logged?

Log each Idris retainer session with: advisory category (wrong dependent type variable in call site; missing base case for totality; structural recursion termination redesign; covering vs total annotation; Refl propositional equality proof; rewrite tactic equality substitution; sym for reversed equality; absurd for unreachable case; Void contradiction; Not a = a -> Void; case analysis induction; implicit argument explicit instantiation; hole-driven development); the specific type names, dependent indices, Nat variables, and pattern cases involved (split : (m : Nat) -> Vect (m + n) a -> (Vect m a, Vect n a); developer passed n where m expected; 1 type error per call site refactor; correct variable passed; errors: 1/call site → 0); and the before/after metric. Include Idris version (1 or 2) and whether fix required correct variable, base case, rewrite tactic, or structural recursion redesign.