关于 ocaml
面向 OCaml 学习者的语言技能指南,重点解释惯用写法背后的原因,并涵盖类型推断、运行时模型、项目结构与测试。
主要功能
- 01类型推断与编译器错误解读
- 02模式匹配与函数式惯用法
- 03运行时、相等性与可变值
- 04Dune 项目和 Alcotest 测试
- 05数据结构与算法指导
适用场景
- 01从 JS、TS 或 PHP 转向 OCaml
- 02理解类型推断错误和编译器消息
- 03搭建 Dune 项目并编写测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ bsene/ skills --skill ocaml安装后在 Claude Code 中输入/ocaml确认已加载。
包含的文件
除 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.md详细介绍
OCaml
OCaml 是一种静态类型、以函数式为主的语言,拥有强大的类型推断引擎(Hindley-Milner)。如果你使用过 JS/TS/PHP,最大的思维转变并非语法,而是要认识到:编译器是设计伙伴,而不是代码检查工具:穷尽式模式匹配和健全的类型系统能在编译时捕获整类错误(空值检查、遗漏分支),而这些错误在你熟悉的语言中通常要到运行时才能发现,甚至根本无法发现。
默认应解释某种写法 为什么 符合惯用风格,而不只是说明 它是什么 ——用户是在学习这门语言,而不只是转换语法。
类型推断:除非用户要求,否则不要添加类型注解
OCaml 几乎总能正确推断类型。除非用户要求添加类型注解(或 .mli 要求),否则编写绑定和函数时不要添加类型注解,例如 let total_ttc price_ht tax_rate = price_ht *. (1. +. tax_rate)。要查看推断出的类型,可以使用 ocamlc -i file.ml、dune utop 或顶层交互环境。阅读类型推断错误时,应按顺序梳理约束:报告的错误是最后一个无法满足的约束,不一定是出错的位置。示例和完整方法:类型推断与编译器错误。
核心语言要点
let是不可变的;需要显式使用ref才能进行变更——应优先考虑重新绑定或递归。match会检查穷尽性;变体类型取代 TS 联合类型和枚举,记录类型取代对象接口,option取代 null,result取代用作控制流的异常。- 异常不会被类型系统追踪;对于预期会发生的失败,默认使用
option/result。 - 所有函数都是柯里化的;
|>用于管道传递;递归需要rec;优先使用带累加器包装函数的尾递归,并且绝不要在循环中使用acc @ [x]。 =表示结构相等,==表示物理相同——这与 JS 开发者的直觉相反;几乎总是应该用=。int是 63 位整数,溢出时会静默回绕;int和float永远不会混用(+.,-.,*.,/.)。- 模块和
.mli签名用于定义接口和隐私;相比类,应优先使用模块、记录和函数。 - 参数的求值顺序未指定;使用
let ... in强制指定顺序。
包含示例的完整速查表(折叠、; 顺序执行、异常、柯里化、尾递归):核心语言速查表。
运行时模型(解释相等性或变更时请使用)
任何关于 = 与 ==、变更或“为什么会这样”的回答,都必须以此模型为依据,并明确说明这一点——这不是可有可无的背景知识:
- OCaml 值要么是未装箱整数,要么是指向堆中块的指针(由一个头字和 N 个值字组成)。
- 元组、数组、记录和非常量变体构造器都只是块——
{ x = 1.; y = 2. }是一个指向 2 字块的指针,因此两个字段相同的记录仍然是分别分配的块。 ref只是一个单字段可变记录,list是由 2 字::单元组成的链,闭包是包含代码指针及其捕获环境的块——这就是为什么ref/:=/!能与其他所有内容组合使用,而不需要成为独立的语言特性。- 因此,
==只比较两个字(时间复杂度为常数,即使面对循环值也是如此);=会遍历块内部,遇到循环时可能永远无法结束。
完整说明:references/runtime-model.md。
项目结构与测试
- 创建新项目:
dune init proj <name>(必要时添加前缀opam exec --),然后使用dune build @check和普通的dune build进行验证。dune build会编译,dune runtest会运行测试,dune exec ./foo.exe会运行可执行文件。绝不要手动编辑生成的.opam文件;应编辑dune-project。 - 单元测试使用 Alcotest;实际项目还会添加基于属性的测试(qcheck)和变异测试(mutaml)。关于设置、stanza 和 Alcotest 结构:dune 与 Alcotest;关于测试类型的选择:测试与质量。
调试
将 dune build 的输出视为事实依据:类型错误通常指向真正的逻辑缺陷。规范消息、警告(8、10、11)和核心类型规则: 类型推断与编译器错误。
默认风格
- 优先使用小型、可组合的函数,而非冗长的命令式代码体——优先使用
List.map/List.filter/List.fold_left,而不是用ref累加器手写循环;除非性能或清晰度确实需要后者。 - 当分支超过两种时,优先使用模式匹配,而不是
if/else链;尤其是处理变体类型时——这不仅更符合惯用写法,还能自动进行穷尽性检查。 - 在合理情况下,让函数保持全定义(对非全定义情况返回
option/result),而不是对预期的失败情况抛出异常;异常应留给真正异常或程序员错误的情况。
表示形式与算法指导
- 对于包含可变值的数组,请检查别名问题:
Array.make n value会共享同一个初始值。若每个元素都需要一个新对象,请使用Array.init。 - 对于需要强制保证不变量的情况,请使用抽象类型;若客户端需要检查值,但构造过程必须受控,则使用私有记录或变体。查看 references/representation-and-invariants.md,了解示例、持久性约定、比较器陷阱和有符号余数。
- 对于自定义集合、递归记忆化、动态规划或图算法,请阅读 references/data-structures-and-algorithms.md。说明表示不变量、算法前置条件,以及最坏情况与摊销成本。
按需阅读
| 阅读时机 | 链接 |
|---|---|
| 阅读类型推断错误、编译器消息或类型规则 | 类型推断与编译器错误 |
| 解释源自 JS/TS/PHP 的语法或语义惯用写法 | 核心语言速查表 |
解释 = 与 ==、可变性或值布局 | 运行时模型 |
| 搭建 dune 项目或编写 Alcotest 测试套件 | Dune 与 Alcotest |
| 选择 qcheck、变异测试、lint 或死代码工具 | 测试与质量 |
| 学习编程练习、习题或从入门到编写真实程序的学习路径 | 学习资源 |
| 书中的具体程序、数据结构或算法 | 使用 OCaml 学习编程 |
| 基准测试结果 | 基准测试 |