强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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.mlUtilsUtils.func
my_module.mlMy_moduleMy_module.func
list.mlListList.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             (* 入口,组合所有模块 *)

扩展阅读