Course Builder个人学习工作台 · 本地模式
OCaml:从类型推导到可测试的命令行工具

01 · 运行第一个程序:认识 OCaml、opam 与 Dune

目标:运行同一个 OCaml 源文件,并区分解释执行、编译和构建工具的职责。

我们最终要做一个学习记录统计器 StudyLog。例如,把 OCaml|25 和 Types|35 两条记录交给它,会得到总计 60 分钟。先不碰文件读取:今天只确认你能把代码变成一个运行结果。

OCaml 的编译器在程序运行前检查类型。类型推导让你经常不必显式标注类型,但不等于动态类型。ocaml 是交互解释器,也能执行源文件;ocamlc 生成字节码程序;ocamlopt 生成本机代码。opam 管理编译器与包,Dune 根据依赖关系构建项目。这四类工作不能互相替代。

准备一个独立环境

先按 OCaml 官方安装文档 安装 opam 与系统编译工具。macOS 可使用 Homebrew 安装 opam;Linux 按发行版准备编译工具;Windows 原生安装见文末链接,本文实验使用 WSL 的 Unix shell。安装系统工具可能需要管理员权限,按官方针对你的系统的说明完成即可。

以下命令在终端执行。已有初始化好的 opam 时跳过第一行;--bare 表示初始化仓库,但不自动建立默认编译器环境。

opam init --bare
mkdir ocaml-study
cd ocaml-study
opam switch create . 5.4.0
eval "$(opam env)"
opam install dune.3.20.2
ocamlc -version
dune --version

opam switch create . 在当前目录建立本地 switch。首次安装会下载并构建依赖,需要等待。eval 把这个 switch 的工具加入当前 shell 的环境。新开终端后先回到 ocaml-study,再次执行 eval "$(opam env)"。这里的目录应当是你新建的练习目录,不要在已有重要项目内重复建环境。

版本检查预期输出分别是 5.4.0 和 3.20.2。也可以使用自己已有的兼容工具链,但不同版本的报错排版可能不同。

实验:源文件不是终端命令

在 ocaml-study 中创建 hello.ml:

let () = print_endline "StudyLog: ready"

print_endline 接收字符串,打印它并换行。let () = 把这次执行安排在文件初始化时;() 是 unit 类型唯一的值,适合表示打印这类操作没有有用的返回数据。下一课会解释绑定。

运行前预测:源文件执行与编译后的程序输出会有区别吗?

ocaml hello.ml
ocamlc -o hello.byte hello.ml
./hello.byte

预期输出(编译本身成功时没有输出):

StudyLog: ready
StudyLog: ready

编译会生成 hello.cmi、hello.cmo 和 hello.byte。这些是练习目录里的构建产物,不用手写。字节码程序运行仍需要 OCaml runtime,不是跨机器通用的独立安装包。

改一次,再看一次

  1. 把字符串改成 StudyLog: 60 minutes,先执行 ocaml hello.ml。
  2. 直接运行旧的 ./hello.byte,它仍输出 ready。源文件改变不会自动改写已经编译出的程序。
  3. 再执行编译命令并运行,两个入口才一致。这正是以后让 Dune 管理重建的理由。

独立练习:让程序分两行打印 StudyLog 和 ready。不能把这两个单词拼成一行。先用源码运行,再编译检查结果。

参考答案与检查方法

将 hello.ml 完整替换为:

let () =
  print_endline "StudyLog";
  print_endline "ready"

分号让两个操作依次发生。重复上面的运行和编译命令,每次运行都应输出两行:

StudyLog
ready

结束后恢复主示例。后续实验不依赖 hello.ml 的内容。

如果出现 command not found,先执行 opam exec -- ocamlc -version;它成功而直接执行失败,说明 shell 没有加载 switch 环境。如果编译成功但输出没变,检查你运行的是源码还是旧产物。如果看到 shell 的语法错误,检查是否把 let 代码粘进了终端而不是 .ml 文件。

本课结束时保留 hello.ml,以及 opam 自动创建的 _opam/。后面的独立实验仍在 ocaml-study 目录执行。

参考资料