About ocaml
An OCaml learning skill for developers, with guidance on idiomatic language use and the reasoning behind it. It covers type inference, runtime behavior, project structure, testing, and algorithms.
Key features
- 01Type inference and compiler error guidance
- 02Pattern matching and functional idioms
- 03Runtime, equality, and mutation
- 04Dune projects and Alcotest testing
- 05Data structure and algorithm guidance
Use cases
- 01Moving from JS, TS, or PHP to OCaml
- 02Understanding inference errors and compiler messages
- 03Setting up Dune projects and tests
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ bsene/ skills --skill ocamlAfter installing, type/ocamlin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/benchmarks.mdcore-language-cheat-sheet.mddata-structures-and-algorithms.mddune-and-alcotest.mdinference-and-compiler-errors.mdlearn-programming-with-ocaml.mdlearning-resources.mdrepresentation-and-invariants.mdruntime-model.mdtesting-and-quality.mdDetails
From the project READMEOCaml
OCaml is a statically-typed, mostly-functional language with a strong type inference engine (Hindley-Milner). Coming from JS/TS/PHP, the biggest mental shift is not syntax — it's that the compiler is a design partner, not a linter: exhaustive pattern matching and a sound type system catch entire classes of bugs (null checks, missed cases) at compile time that you're used to catching at runtime or not at all.
Default to explaining why something is idiomatic, not just that it is — the user is learning the language, not just translating syntax.
Type inference: don't annotate unless asked
OCaml infers types almost always correctly. Unless the user asks for annotations (or a .mli requires them), write bindings and functions without type annotations, e.g. let total_ttc price_ht tax_rate = price_ht *. (1. +. tax_rate). To see inferred types, use ocamlc -i file.ml, dune utop, or the toplevel. When reading an inference error, walk the constraints in order: the reported error is the last unsatisfiable constraint, not necessarily the mistake site. Examples and the full method: type inference and compiler errors.
Core language essentials
letis immutable; mutation needs an explicitref— prefer rebinding/recursion first.matchis exhaustiveness-checked; variants replace TS unions/enums, records replace object interfaces,optionreplaces null,resultreplaces exceptions-as-control-flow.- Exceptions are untracked by the type system; default to
option/resultfor expected failure. - Every function is curried;
|>pipes;recis required for recursion; prefer tail recursion with an accumulator wrapper, and neveracc @ [x]in a loop. =is structural,==is physical identity — the reverse of a JS dev's instinct;=is almost always right.intis 63-bit and wraps silently;intandfloatnever mix (+.,-.,*.,/.).- Modules plus
.mlisignatures are the interface/privacy mechanism; prefer modules + records + functions over classes. - Evaluation order of arguments is unspecified; force order with
let ... in.
Full cheat sheet with examples (folds, ; sequencing, exceptions, currying, tail recursion): core language cheat sheet.
Runtime model (use this when explaining equality or mutation)
Ground any answer about = vs ==, mutation, or "why is it like that" in this model, and state it explicitly — it is not optional background:
- An OCaml value is either an unboxed integer or a pointer to a heap block (a header word + N value words).
- Tuples, arrays, records, and non-constant variant constructors are all just blocks —
{ x = 1.; y = 2. }is a pointer to a 2-word block, so two records with identical fields are still two separately allocated blocks. - A
refis just a one-field mutable record, alistis a chain of 2-word::cells, a closure is a block with a code pointer plus its captured environment — which is whyref/:=/!compose with everything else instead of being a distinct language feature. - Consequence for equality:
==compares only the two words (constant time, even on cyclic values);=walks into the blocks and can loop forever on a cycle.
Full walkthrough: references/runtime-model.md.
Project structure and testing
- New project:
dune init proj <name>(prefixopam exec --if needed), then validate withdune build @checkand a plaindune build.dune buildcompiles,dune runtestruns tests,dune exec ./foo.exeruns executables. Never hand-edit the generated.opam; editdune-project. - Unit tests use Alcotest; real projects add property-based testing (qcheck) and mutation testing (mutaml). Setup, stanzas, and Alcotest shape: dune and Alcotest; test-type selection: testing and quality.
Debugging
Treat dune build output as ground truth: type errors usually point at a real logic gap. Canonical messages, warnings (8, 10, 11), and core typing rules: type inference and compiler errors.
Style defaults
- Favor small, composable functions over long imperative bodies —
List.map/List.filter/List.fold_leftover hand-rolled loops withrefaccumulators, unless performance or clarity genuinely calls for the latter. - Favor pattern matching over
if/elsechains once there are more than two cases, especially over a variant type — it's both more idiomatic and gets exhaustiveness checking for free. - Keep functions total where reasonable (return
option/resultfor partiality) rather than raising exceptions for expected failure cases; reserve exceptions for genuinely exceptional/programmer-error conditions.
Representation and algorithm guidance
- For arrays containing mutable values, check aliasing:
Array.make n valueshares the same initializer. UseArray.initwhen each element needs a fresh object. - For enforced invariants, use an abstract type; use a private record or variant when clients should inspect values but construction must stay controlled. Read references/representation-and-invariants.md for examples, persistence contracts, comparator pitfalls, and signed remainders.
- For custom collections, recursive memoization, dynamic programming, or graph algorithms, read references/data-structures-and-algorithms.md. Explain the representation invariant, algorithm preconditions, and worst-case versus amortized costs.
Read On Demand
| Read when | Link |
|---|---|
| Reading inference errors, compiler messages, or typing rules | Inference and compiler errors |
| Explaining a syntax or semantic idiom coming from JS/TS/PHP | Core language cheat sheet |
Explaining = vs ==, mutation, or value layout | Runtime model |
| Scaffolding dune projects or writing Alcotest suites | Dune and Alcotest |
| Choosing qcheck, mutation testing, linting, or dead-code tools | Testing and quality |
| Katas, exercises, or a beginner-to-real-programs learning path | Learning resources |
| Concrete programs, data structures, or algorithms from the book | Learn Programming with OCaml |
| Benchmark results | Benchmarks |