Blog › ICP guides
Yeti developer on retainer: type system, int vs num distinction, JVM interop, lambda functions, and Yeti ML-style functional programming on monthly retainer
September 26, 2026 · ~15 min read
A Yeti data processing program was computing statistical summaries over a list of measurements. The developer had written a function with explicit type annotations: the accumulator parameter was annotated as int and the count parameter was annotated as int. Inside the function body, the developer computed a running average by dividing the accumulated sum by the count using the / division operator and passing the result into a downstream function whose parameter was also annotated as int. In Yeti, the / operator on numeric values returns num — the numeric type class that covers both integer and floating-point values — not int, which is a distinct concrete type for integer arithmetic. Passing a num result where an int was explicitly annotated produced a type error at every call site. Type errors per annotation site: 2. The developer restructured the annotations: the accumulator and count parameters were changed from int to num to reflect the actual type of values flowing through the function, and where an integer result was explicitly required (for an index computation downstream), the developer replaced / with Yeti’s integer division operator \\ which divides and truncates to an int result. Type errors per annotation site: 2 → 0. The Yeti developer on retainer diagnosed the annotation precision mismatch: int in Yeti is a concrete monomorphic type that rejects any value produced by floating-point or division arithmetic; num is the type class that generalizes over numeric values and is the correct annotation for computations that may produce non-integer intermediate results, including any use of the / operator.
The work log entry read “fixed statistics pipeline type errors, 7h.” It names the result and duration. It cannot explain why the int vs num distinction is non-obvious in Yeti — Yeti’s type inference is designed to infer the most general type class rather than forcing annotators to choose between int and num at every site; explicit type annotations are usually added for documentation or to narrow inference, not to force concrete types; a developer who annotates int expecting “a number that happens to be whole” is applying the semantics of a dynamically typed language where a number is a number, not Yeti’s semantics where int is a static concrete type that rejects the result of /. It cannot explain when Yeti’s type class inference generalizes correctly and when it requires explicit annotation — Yeti infers the most-general type class that satisfies all constraints in the function body; a function that uses only addition and multiplication on its parameters can be inferred as polymorphic over num; a function that uses bitwise operations is inferred as requiring int; a function that uses both division and bitwise operations requires the developer to choose a specific type and convert explicitly at the boundary. It cannot explain Yeti’s struct type system — a struct type {field is type} defines fields with static types checked at compile time; a hash type hash<string> defines a dynamic string-keyed map with values of a given type; choosing between struct (compile-time field checking) and hash (dynamic key access) shapes the error surface of the program and the amount of type information the compiler can propagate. The 7 hours of type annotation audit, integer division operator selection, and struct vs hash restructuring are invisible in the diff.
Yeti type system: int, num, type class inference, division operators, and explicit coercion
Yeti’s numeric type hierarchy distinguishes between int (a concrete 64-bit integer type), long (alias for int in most contexts), and num (a type class that covers integers, longs, and floating-point values). This distinction exists because Yeti targets the JVM where integer and floating-point arithmetic are different operations at the bytecode level; the type class system allows Yeti to write polymorphic numeric functions that work on any numeric type without requiring separate implementations. The / division operator returns num regardless of operand types: dividing two int values with / returns a num result because the operation may produce a non-integer quotient. The \\ integer division operator divides and truncates to an int result: it is the correct operator when the program needs an integer quotient for indexing, counting, or other contexts that require int. The int() function explicitly coerces a num value to int by truncation, and is used at JVM interop boundaries where a Java method requires an integer argument but the Yeti computation produces a num.
Yeti’s type inference works from the function body outward: it infers the most general type that satisfies all operations performed on a value. Explicit type annotations narrow this inference. The common annotation error is annotating a parameter as int when the function body performs operations that produce num results — the annotation is more specific than the function body requires, and the compiler rejects any call site that passes a num value. Removing the annotation and letting Yeti infer the type often resolves the mismatch by allowing the compiler to generalize to num where the function body is compatible with it. When annotations are added for documentation purposes, the correct approach is to annotate parameters as num for any value that may result from division or floating-point arithmetic, and int only for values that are required to be exact integers for semantic reasons (array indices, counts, loop bounds). Yeti was developed by Madis Mõtus and first released around 2007. Its primary use cases are JVM-based functional scripting, data transformation pipelines that benefit from ML-style type inference, and interop-heavy programs that call Java libraries from a functional language surface. Its closest retainer neighbors are Scala developer retainers (both target the JVM with functional type systems) and Kotlin developer retainers (JVM language with similar interop patterns), but Yeti’s ML-derived structural type class inference, int-vs-num division operator distinction, and backslash lambda syntax make the retainer work distinct.
Yeti JVM interop: import syntax, Java type mapping, exception boundaries, and numeric conversion
Yeti JVM interop uses the import statement to bind Java class names: import java.util.ArrayList as JList makes JList available as a Yeti binding. Java method calls use dotcall notation: list.add(item) calls the add method on a Java object. Constructors use the new expression: new JList() calls the default constructor and returns a Yeti value wrapping the Java object. Java primitive numeric types (int, long, double, float) are mapped to Yeti’s num type at the interop boundary; calling a Java method that returns a Java int produces a Yeti num value, which must be explicitly coerced to Yeti int via int() if the downstream computation requires it. Java String objects map to Yeti’s string type. Java boolean maps to Yeti’s boolean. Java exceptions are caught in Yeti’s try ... catch blocks; the catch clause can pattern-match on the exception class name to handle specific exception types from Java APIs.
Struct and hash selection is the second most common type design issue in Yeti retainer work. A Yeti struct {name is string, value is num} defines a record type with statically checked field access: accessing record.name is verified at compile time to be a string, and accessing a nonexistent field is a compile error. A Yeti hash hash<string> is a dynamic string-keyed map where field access uses hash.[key] notation and returns an option type that must be unwrapped. The struct form is the correct choice when the set of fields is known at compile time and type checking on field access is valuable; the hash form is correct when keys are dynamic (computed at runtime, received from external data, or varying in count). A common design error in Yeti programs is using hashes for all record-like data to avoid writing struct type annotations, which eliminates compile-time field checking and pushes errors to runtime. Retainer work involves auditing hash usage, identifying cases where the key set is statically determined, and converting to struct types to restore compile-time correctness guarantees.
How HourTab tracks Yeti developer retainer hours
Yeti retainer work carries the invisible-hours problem specific to type-inferred ML-family languages: the compiler rejects the program with a type error, but the error message refers to the inferred type at the point of mismatch rather than the source of the type mismatch in the annotation; tracing from the error message back to the root cause (an annotation that is more concrete than the computation requires) requires understanding Yeti’s type inference algorithm and numeric type hierarchy. The int-vs-num mismatch pattern described above is the most common type error in Yeti programs written by developers coming from dynamically typed languages: they add explicit type annotations to document intent, use int to mean “a number”, and encounter type errors only when the computation produces a num that the annotation rejects. Diagnosing this requires understanding the concrete meaning of int in Yeti’s type system, the difference between the / and \\ division operators, and how to annotate numeric functions with the correct type class. A retainer engagement typically involves annotation audit (every explicit type annotation verified for consistency with the computation in the function body), operator audit (/ usage verified for whether the result is used as int or num downstream), and struct-vs-hash audit (hash usage with static key sets identified as candidates for struct conversion).
HourTab gives Yeti developers a public retainer-hours URL they send to clients — typically JVM teams building functional scripting layers over existing Java libraries, data transformation pipelines that benefit from ML-style type safety with JVM deployment, and consultants integrating Yeti programs into Java build systems for configuration-driven data processing. For Yeti retainers, each work log entry should name the mechanism (type: int annotation, num type class, type class inference, explicit coercion int(); operator: / division returns num, \\ integer division returns int, modulo mod; struct: {field is type} compile-time field access, hash<string> dynamic key access; JVM: import, class.method(), new Constructor(), Java type mapping, exception catch), the specific function name, annotation, and before/after type error count. Yeti retainers are often compared to Scala developer retainers for the shared JVM functional context, but Yeti’s ML-derived structural type inference, int-vs-num numeric type class distinction, backslash lambda syntax, and struct-vs-hash design choice make the retainer work distinct in numeric type annotation engineering, type inference constraint design, and JVM interop boundary management. HourTab’s work log makes the type annotation audit, division operator selection, and struct-vs-hash restructuring visible to clients who would otherwise see only the symptom — type errors at annotation sites — and not understand why the fix required knowing that Yeti’s int is a concrete type that rejects the result of /, and why the correct annotation for most numeric computations is num, and why the \\ operator is the one that produces an integer quotient suitable for int-annotated downstream parameters.
Track Yeti developer retainer hours without the status emails
HourTab gives Yeti 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 type annotation audit log — int-vs-num diagnosis, integer division restructuring, struct-vs-hash design — becomes the proof of value that gets the retainer renewed.
See HourTab pricing →FAQ: Yeti developer retainers
What does a Yeti developer on retainer typically do?
A Yeti developer on monthly retainer covers Yeti type system (int distinct concrete integer type; num numeric type class; string; boolean; unit (); list; hash; struct with {field is type}; variant for algebraic data; type class inference inferring most-general class; num-to-int coercion via int()), Yeti functions and combinators (backslash lambda \x -> expr; function composition .; partial application; map, filter, fold list operations; string.split; do ... done imperative block; for and while; try ... catch), and Yeti JVM interop (import Java.lang.String as JString; class.method() calls; new JavaClass(args) constructor; Java numeric types mapped to num; Java String to Yeti string; .class.field direct access; Java exception catching).
What Yeti work is most commonly underlogged in a retainer?
Int-vs-num type mismatch diagnosis (developer annotated accumulator as int and passed result of / division as argument; type errors: 2/annotation; changed annotation to num and used \\ integer division where integer required; type errors: 2/annotation → 0; 5–9 hrs invisible); type class inference design (function annotated as int instead of num narrowed inference too aggressively; removing annotation let Yeti generalize to num; 4–8 hrs invisible); struct vs hash selection (struct fields statically typed and checked at compile time; hash keys dynamic; converting hash with static keys to struct to restore compile-time checking; 4–7 hrs invisible); JVM interop boundary (int() coercion required for Java numeric results used as Yeti int; Java exception class mapping in catch; 3–6 hrs invisible).
What are typical Yeti developer retainer rates?
Entry-level Yeti developers (1–2 years, basic functional patterns, list operations, yeti workflow) bill at $55–$100/hr. Mid-level Yeti ML programmers (2–4 years, int-vs-num type annotation design, struct and variant type modeling, function composition and partial application, JVM library integration) bill at $90–$165/hr. Senior Yeti JVM functional developers (4–8 years, type class constraint design, complex Java API interop, JVM performance optimization, large-scale Yeti application architecture) bill at $130–$245/hr. Monthly retainer ranges: $2,000–$4,000/mo advisory (15–25 hrs), $5,500–$14,000/mo for full Yeti JVM functional systems engineering.
What should a Yeti developer retainer agreement include?
A Yeti developer retainer agreement should specify: type system scope (int concrete vs num type class; type class inference rules; int() explicit coercion; struct field typing vs hash key dynamics); function scope (backslash lambda; composition; partial application; list operations; imperative blocks; exception handling); numeric operator scope (/ returns num; \\ integer division returns int; mod; which operators require int vs num); JVM interop scope (import syntax; method invocation; constructor; Java type mapping; exception catching); and hour logging format (type annotation, integer division, struct design, JVM interop; function name and before/after type error count).
How should Yeti developer retainer hours be logged?
Log each Yeti retainer session with: advisory category (type annotation: int vs num, struct field type, type class inference; numeric operator: / vs \\, int() coercion, mod; list: map, filter, fold, string.split; JVM: import, class.method(), new, Java type mapping, exception catch); the specific function name, annotation, and before/after type error count (function: accumulate; annotation: int; passed value: total / count returning num; type errors: 2/annotation; fix: annotation changed to num; integer result computed via total \\ count; type errors: 2/annotation → 0); and the before/after metric. Include whether fix required annotation type change, integer division operator substitution, explicit int() coercion, or struct-vs-hash restructuring.