AgentSkillsCN

ocaml-code-style

OCaml 编码风格与重构模式。当用户请求对 OCaml 代码进行整理、清理、重构或优化,或希望提升代码质量、强化命名规范、降低代码复杂度时,可参考本技能。

SKILL.md
--- frontmatter
name: ocaml-code-style
description: "OCaml coding style and refactoring patterns. Use when the user asks to tidy, clean up, refactor, or improve OCaml code, reviewing code quality, enforcing naming conventions, or reducing complexity."
license: ISC

OCaml Code Style

Core Philosophy

  1. Interface-First: Design .mli first. Clean interface > clever implementation.
  2. Modularity: Small, focused modules. Compose for larger systems.
  3. Simplicity (KISS): Clarity over conciseness. Avoid obscure constructs.
  4. Explicitness: Explicit control flow and error handling. No exceptions for recoverable errors.
  5. Purity: Prefer pure functions. Isolate side-effects at edges.
  6. NEVER use Obj.magic: Breaks type safety. Always a better solution.

Naming Conventions

ElementConventionExample
Fileslowercase_underscoresuser_profile.ml
ModulesSnake_caseUser_profile
Typessnake_case, primary type is ttype user_profile, type t
Valuessnake_casefind_user, create_channel
VariantsSnake_caseWaiting_for_input, Processing_data

Function naming:

  • find_* returns option (may not exist)
  • get_* returns value directly (must exist)

Avoid: Long names with many underscores (get_user_profile_data_from_database_by_id).

Refactoring Patterns

Option/Result Combinators

ocaml
(* Before *)
match get_value () with Some x -> Some (x + 1) | None -> None

(* After *)
Option.map (fun x -> x + 1) (get_value ())

Prefer: Option.map, Option.bind, Option.value, Result.map, Result.bind

Monadic Syntax (let*/let+)

ocaml
(* Before - nested matches *)
match fetch_user id with
| Ok user -> (match fetch_perms user with Ok p -> Ok (user, p) | Error e -> Error e)
| Error e -> Error e

(* After *)
let open Result.Syntax in
let* user = fetch_user id in
let+ perms = fetch_perms user in
(user, perms)

Pattern Matching Over Conditionals

ocaml
(* Before *)
if x > 0 then if x < 10 then "small" else "large" else "negative"

(* After *)
match x with
| x when x < 0 -> "negative"
| x when x < 10 -> "small"
| _ -> "large"

Function Design

Keep functions small: Under 50 lines. One purpose per function.

Avoid deep nesting: Max 4 levels of match/if. Extract helpers.

High complexity signal: Many branches = split into focused helpers.

ocaml
(* Bad - high complexity *)
let check x y z =
  if x > 0 then if y > 0 then if z > 0 then ... else ... else ... else ...

(* Good - factored *)
let all_positive x y z = x > 0 && y > 0 && z > 0
let check x y z = if not (all_positive x y z) then "invalid" else ...

Error Handling

Use result for recoverable errors. Exceptions only for programming errors.

Never catch-all:

ocaml
(* Bad *)
try f () with _ -> default

(* Good *)
try f () with Failure _ -> default

Don't silence warnings: Fix the issue, don't use [@warning "-nn"].

Library Preferences

Instead ofUseWhy
StrReBetter API, no global state
PrintfFmtComposable, type-safe
yojson (manual)jsontType-safe codecs

Module Hygiene

Abstract types: Keep type t abstract. Expose smart constructors.

ocaml
(* Good - .mli *)
type t
val create : name:string -> t
val name : t -> string
val pp : t Fmt.t

Avoid generic names: Not Util, Helpers. Use String_ext, Json_codec.

API Design

Avoid boolean blindness:

ocaml
(* Bad *)
let create_widget visible bordered = ...
let w = create_widget true false  (* What does this mean? *)

(* Good *)
type visibility = Visible | Hidden
let create_widget ~visibility ~border = ...

Red Flags

  • Match that just rewraps: Some v -> Some (f v) | None -> None
  • Nested Result/Option matches → use let*/let+
  • Deep if/then/else → pattern matching
  • Missing pp function on types
  • Unlabeled boolean parameters
  • Obj.magic anywhere