OCaml 教程 / OCaml 模块系统基础
OCaml 模块系统基础
OCaml 的模块系统是语言最强大的特性之一,提供了代码组织、封装和抽象的完整机制。本文从基础概念出发,系统讲解模块定义、使用和最佳实践。
模块定义:module / struct / end
OCaml 中每个 .ml 文件自动成为一个模块,也可以用 module ... = struct ... end 显式定义模块。
文件级模块
(* math_utils.ml *)
let pi = 3.14159265358979
let circle_area r = pi *. r *. r
let circle_circumference r = 2.0 *. pi *. r
这个文件自动形成 Math_utils 模块(首字母大写)。
显式模块定义
module Math = struct
let pi = 3.14159265358979
let circle_area r = pi *. r *. r
let circle_circumference r = 2.0 *. pi *. r
end
(* 使用模块 *)
let area = Math.circle_area 5.0
let () = Printf.printf "Area: %f\n" area
嵌套模块
module Geometry = struct
module Circle = struct
let area r = 3.14159265358979 *. r *. r
let circumference r = 2.0 *. 3.14159265358979 *. r
end
module Rectangle = struct
let area w h = w *. h
let perimeter w h = 2.0 *. (w +. h)
end
end
let () =
Printf.printf "Circle area: %f\n" (Geometry.Circle.area 3.0);
Printf.printf "Rect area: %f\n" (Geometry.Rectangle.area 4.0 5.0)
打开模块:open
open 指令将模块中的标识符引入当前作用域,避免重复书写模块名。
(* 不使用 open *)
let a = List.length [1; 2; 3]
let b = String.concat ", " ["hello"; "world"]
(* 使用 open *)
open List
let a = length [1; 2; 3]
(* 局部 open(OCaml 4.00+) *)
let b = String.(concat ", " ["hello"; "world"])
(* 让表达式局部 open *)
let len = let open List in length [1; 2; 3]
⚠️ 注意:全局
open可能导致命名冲突。推荐优先使用局部open或模块别名来限定。
模块别名
module L = List
module S = String
module H = Hashtbl
let () =
let words = S.split_on_char ' ' "hello world ocaml" in
Printf.printf "Word count: %d\n" (L.length words)
include 指令
include 将一个模块的所有成员合并到当前模块中,用于模块组合和继承。
module PrintableInt = struct
include Int (* 继承 Int 的所有函数 *)
let to_string = string_of_int
let print n = print_endline (to_string n)
end
let () = PrintableInt.(print (add 3 4))
(* 输出: 7 *)
include 的组合模式
module Comparable = struct
type t = int
let compare = Stdlib.compare
let equal a b = compare a b = 0
end
module Printable = struct
type t = int
let to_string = string_of_int
end
module FullInt = struct
include Comparable
include Printable
end
let () =
Printf.printf "equal: %b\n" (FullInt.equal 3 3);
Printf.printf "repr: %s\n" (FullInt.to_string 42)
模块与文件的映射关系
| 文件名 | 自动模块名 | 引用方式 |
|---|---|---|
utils.ml | Utils | Utils.func |
my_module.ml | My_module | My_module.func |
list.ml | List | List.func(注意与标准库冲突) |
💡 提示:文件名中的
-在模块名中会变成_。如my-utils.ml对应模块My_utils。
.mli 接口文件
.mli 文件定义模块的公开接口(签名),控制哪些类型和值对外可见。
(* stack.mli *)
type 'a t
val empty : 'a t
val push : 'a -> 'a t -> 'a t
val pop : 'a t -> ('a * 'a t) option
val is_empty : 'a t -> bool
(* stack.ml *)
type 'a t = 'a list
let empty = []
let push x xs = x :: xs
let pop = function
| [] -> None
| x :: xs -> Some (x, xs)
let is_empty = function
| [] -> true
| _ -> false
使用 .mli 的好处:
| 特性 | 说明 |
|---|---|
| 信息隐藏 | 类型 t 在 .mli 中是抽象的,外部无法构造 |
| 编译时检查 | .mli 和 .ml 不匹配会编译报错 |
| 文档化 | .mli 是模块的契约文档 |
| 独立编译 | 修改 .ml 不影响使用该模块的代码 |
Stdlib 标准库模块概览
| 模块 | 用途 | 常用函数 |
|---|---|---|
List | 不可变链表 | map, filter, fold_left, length |
Array | 可变数组 | get, set, length, make |
String | 字符串操作 | length, sub, concat, split_on_char |
Hashtbl | 哈希表 | create, add, find_opt, iter |
Map | 函数式映射 | empty, add, find_opt, fold |
Set | 函数式集合 | empty, add, mem, fold |
Printf | 格式化输出 | printf, sprintf, fprintf |
Format | 美化打印 | fprintf, pp_print_string |
Option | 可选值 | map, value, bind, is_some |
Result | 错误处理 | map, bind, value, is_ok |
Seq | 惰性序列 | map, filter, fold_left |
Buffer | 可变字符串缓冲 | create, add_string, contents |
使用示例
(* Option 模块 *)
let safe_div a b =
if b = 0 then None
else Some (a / b)
let result = Option.map (fun x -> x * 2) (safe_div 10 3)
let () = Option.iter (Printf.printf "Result: %d\n") result
(* Map 模块 *)
module IntMap = Map.Make(Int)
let scores =
IntMap.empty
|> IntMap.add 1 95
|> IntMap.add 2 87
|> IntMap.add 3 92
let () =
IntMap.iter (fun k v ->
Printf.printf "Student %d: %d\n" k v
) scores
编译多模块项目
# 单文件编译
ocamlc -o main math_utils.ml main.ml
# 使用 ocamlopt(优化编译)
ocamlopt -o main math_utils.ml main.ml
# 使用 dune 构建系统(推荐)
对应的 dune 文件:
(executable
(name main)
(libraries stdlib))
实际业务场景
场景一:配置管理模块
(* config.ml *)
type t = {
host : string;
port : int;
debug : bool;
}
let default = {
host = "localhost";
port = 8080;
debug = false;
}
let load () =
(* 实际项目中从文件或环境变量读取 *)
default
let get_host cfg = cfg.host
let get_port cfg = cfg.port
let is_debug cfg = cfg.debug
场景二:分层架构
src/
├── models/
│ ├── user.ml (* 用户模型 *)
│ └── product.ml (* 产品模型 *)
├── services/
│ ├── auth.ml (* 认证服务,依赖 User *)
│ └── catalog.ml (* 目录服务,依赖 Product *)
└── main.ml (* 入口,组合所有模块 *)