Help us improve
Share bugs, ideas, or general feedback.
From ocaml-dev
Guides OCaml development with best practices for writing code, designing .mli interfaces, result-based error handling, dune builds, and libraries like eio, fmt, logs, cmdliner, yojson, cohttp-eio.
npx claudepluginhub avsm/ocaml-claude-marketplace --plugin ocaml-devHow this skill is triggered — by the user, by Claude, or both
Slash command
/ocaml-dev:ocamlThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
1. **Interface-First Design**: Design the `.mli` file first. A clean interface matters more than clever implementation.
Enforces OCaml coding style including naming conventions, modularity, refactoring patterns like Option/Result combinators and monadic syntax, plus error handling to improve code quality and reduce complexity.
Guides OCaml development with opam for packages, dune for builds/tests, merlin for editor support, ocamlformat for formatting. Includes project setup workflows and examples.
Guides Gleam type system usage: custom types, ADTs, pattern matching, generics, type inference, opaque types, exhaustive checks, and Result/Option for type-safe BEAM apps.
Share bugs, ideas, or general feedback.
.mli file first. A clean interface matters more than clever implementation.dune exclusivelydune fmt before committing (uses ocamlformat)eiofmtlogscmdlineryojsoncohttp-eioEvery .mli file starts with a top-level doc comment:
(** User API
This module provides types and functions for interacting with users. *)
Use [function_name arg1 arg2] is ... pattern:
val is_bot : t -> bool
(** [is_bot u] is [true] if [u] is a bot user. *)
For values, describe what they represent:
type id = string
(** A user identifier. *)
For modules with a central type t, provide these functions where applicable:
| Function | Purpose |
|---|---|
val v : ... -> t | Pure smart constructor (no I/O) |
val create : ... -> (t, Error.t) result | Constructor with side-effects |
val pp : t Fmt.t | Pretty-printer for logging/debugging |
val equal : t -> t -> bool | Structural equality |
val compare : t -> t -> int | Comparison for sorting |
val of_json : Yojson.Safe.t -> (t, string) result | Parse from JSON |
val to_json : t -> Yojson.Safe.t | Serialize to JSON |
val validate : t -> (t, string) result | Validate data integrity |
Keep types abstract (type t) when possible. Expose smart constructors and accessors instead of record fields to maintain invariants.
Use result type for recoverable errors. Reserve exceptions for programming errors (e.g., Invalid_argument).
Define a comprehensive error type in lib/error.ml:
(* In lib/error.mli *)
type t = [
| `Api of string * Yojson.Safe.t
| `Json_parse of string
| `Network of string
| `Msg of string
]
val pp : t Fmt.t
let err_api code fields = Error (`Api (code, fields))
let err_parse msg = Error (`Json_parse msg)
let find_user_id json =
match Yojson.Safe.Util.find_opt "id" json with
| Some (`String id) -> Ok id
| Some _ -> err_parse "Expected string for user ID"
| None -> err_parse "Missing user ID"
try ... with _ -> .... Match specific exceptions.Fmt.failwith:let tls_config =
match Tls.Config.client ~authenticator () with
| Ok config -> config
| Error (`Msg msg) -> Fmt.failwith "Failed to create TLS config: %s" msg
match/if signals need for refactoring.bin/, lib/ui/).Use the logs library with per-module log sources:
let log_src = Logs.Src.create "project_name.module_name"
module Log = (val Logs.src_log log_src : Logs.LOG)
| Level | Use Case |
|---|---|
Log.app | Messages always shown to user (startup) |
Log.err | Handled but critical errors |
Log.warn | Potential issues, operation continues |
Log.info | Informational state messages |
Log.debug | Verbose debugging details |
Log.info (fun m ->
m "Received event: %s" event_type
~tags:(Logs.Tag.add "channel_id" channel_id Logs.Tag.empty))
| Element | Convention | Example |
|---|---|---|
| Files | lowercase_underscores | user_profile.ml |
| Modules | lowercase_underscores | user_profile |
| Primary type | t | type t |
| Identifiers | id | type id = string |
| Values | short_descriptive | find_user, create_channel |
Use labels only when they clarify meaning. Avoid ~f and ~x.
For bin/ applications using cmdliner:
bin/common.mlrun function that initializes the main loop and environment (e.g., Eio loop)Follow Conventional Commits:
Format: type(scope): subject
| Type | Purpose |
|---|---|
feat | New feature |
fix | Bug fix |
docs | Documentation |
style | Formatting |
refactor | Code restructuring |
test | Tests |
chore | Maintenance |
Examples:
feat(api): add support for file uploadsfix(ui): correct channel list rendering bugtest(user): add tests for user profile updates