OCaml 教程 / 多态与受限多态
多态与受限多态
OCaml 拥有强大的类型系统,多态(polymorphism)是其核心特性之一。本文将系统讲解泛型函数、类型变量、受限多态、值限制等关键概念。
1. 泛型函数与类型变量
1.1 基本泛型函数
泛型函数可以处理多种类型的数据,而不丢失类型安全性:
(* 最简单的多态函数 *)
let id x = x
(* 类型: 'a -> 'a *)
let swap (a, b) = (b, a)
(* 类型: 'a * 'b -> 'b * 'a *)
let compose f g x = f (g x)
(* 类型: ('b -> 'c) -> ('a -> 'b) -> 'a -> 'c *)
1.2 类型变量 'a
OCaml 中以单引号开头的标识符是类型变量:
| 类型变量 | 含义 | 示例 |
|---|---|---|
'a | 任意类型 | id : 'a -> 'a |
'a * 'b | 两个独立类型 | fst : 'a * 'b -> 'a |
'a list | 同类型列表 | List.length : 'a list -> int |
'a option | 可选值 | Option.is_some : 'a option -> bool |
(* 类型变量可以出现在多个位置,表示同一类型 *)
let pair x = (x, x)
(* 类型: 'a -> 'a * 'a —— 两个位置必须相同 *)
(* 不同的类型变量表示独立类型 *)
let ignore_second a _b = a
(* 类型: 'a -> 'b -> 'a *)
⚠️ 注意点:类型变量以 'a、'b、'c 等命名,但 '1、'2 这样的编号形式是弱类型变量(weak type variables),通常意味着值限制被触发。
2. 多态函数的类型推导
Hindley-Milner 类型系统允许编译器自动推导多态函数的类型:
(* 编译器推导过程 *)
let rec map f = function
| [] -> []
| x :: xs -> f x :: map f xs
(* 推导结果: ('a -> 'b) -> 'a list -> 'b list *)
推导规则
| 表达式 | 推导规则 | 结果类型 |
|---|---|---|
let x = 1 | literal | int |
let f x = x | identity | 'a -> 'a |
let f x = x + 1 | 约束为 int | int -> int |
let f x y = (x, y) | pair | 'a -> 'b -> 'a * 'b |
3. Let-Polymorphism
OCaml 采用 let-polymorphism(let 多态),即 let 绑定的函数可以在不同位置以不同类型使用:
let id = fun x -> x
(* 在不同上下文中使用 *)
let n : int = id 42 (* id 作为 int -> int *)
let s : string = id "hello" (* id 作为 string -> string *)
Let-Polymorphism vs Lambda-Polymorphism
(* ✅ let-polymorphism: OK *)
let id x = x
let pair = (id 42, id "hello")
(* ❌ lambda-polymorphism: 不能在同一个 lambda 内多态使用 *)
(* fun x -> (x 42, x "hello") (* 错误!x 不能同时是 int->int 和 string->string *) *)
💡 提示:let-polymorphism 是 ML 家族语言的特色,比 Java/C# 的泛型更灵活,因为它不需要在定义时声明类型参数。
4. 值限制(Value Restriction)
4.1 问题背景
值限制是 OCaml 类型系统中一个重要的安全机制,防止可变引用破坏类型安全:
(* 如果没有值限制,以下代码会导致类型错误 *)
let r = ref [] (* 这里会触发值限制 *)
(* 不能让它同时成为 int list ref 和 string list ref *)
4.2 值限制规则
| 表达式类别 | 是否为值(value) | 是否多态化 |
|---|---|---|
常量 42、"hello"、[] | ✅ 是值 | ✅ 多态化 |
变量 x | ✅ 是值 | ✅ 多态化 |
函数 fun x -> x | ✅ 是值 | ✅ 多态化 |
函数应用 f x | ❌ 非值 | ❌ 不多态化 |
构造器应用 Some x | ❌ 非值 | ❌ 不多态化 |
ref [] | ❌ 非值 | ❌ 不多态化 |
(* ✅ OK —— 是值 *)
let empty = []
let id = fun x -> x
(* ❌ 触发值限制 —— 函数应用不是值 *)
let r = ref []
(* ✅ 修复方式 1:加类型注解 *)
let r : int list ref = ref []
(* ✅ 修复方式 2:使用泛型函数 *)
let make_ref () = ref []
4.3 弱类型变量
当值限制被触发时,OCaml 会分配弱类型变量:
# let r = ref [];;
val r : '_weak1 list ref = {contents = []}
(* '_weak1 是弱类型变量,一旦使用就会被固定 *)
r := [42];
(* r 的类型变成 int list ref *)
⚠️ 注意点:弱类型变量以 '_ 开头,意味着该类型位置只能被固定一次。如果看到 '_weak 类型,通常是值限制的信号。
5. 受限多态(Constrained Polymorphism)
5.1 使用模块约束
OCaml 不直接支持类似 Haskell 的 type class,但可以通过模块系统实现受限多态:
(* 通过 module type 约束多态函数 *)
module type Comparable = sig
type t
val compare : t -> t -> int
end
module MakeSet (C : Comparable) : sig
type elt = C.t
type t
val empty : t
val add : elt -> t -> t
val mem : elt -> t -> bool
end = struct
type elt = C.t
type t = elt list
let empty = []
let add x xs = if List.mem x xs then xs else x :: xs
let mem = List.mem
end
5.2 使用 record 模拟类型类
(* 字典传递风格 *)
type 'a show = { show : 'a -> string }
let show_int = { show = string_of_int }
let show_string = { show = fun s -> "\"" ^ s ^ "\"" }
let show_list show_a =
{ show = fun xs ->
"[" ^ String.concat "; " (List.map show_a.show xs) ^ "]" }
(* 多态函数接受 show 字典 *)
let print_any show_a x = print_endline (show_a.show x)
5.3 使用 first-class module
let print_sorted (type a) (module Ord : Set.OrderedType with type t = a) xs =
let module S = Set.Make(Ord) in
let s = List.fold_right S.add xs S.empty in
S.elements s
6. 多态递归(Polymorphic Recursion)
多态递归是指递归调用时使用与外层不同的类型实例化:
(* 类型: int -> (int -> int) -> int *)
let rec apply_n n f =
if n <= 0 then fun x -> x
else compose f (apply_n (n - 1) f)
(* 真正的多态递归 —— 类型树 *)
type 'a tree =
| Leaf of 'a
| Node of 'a tree list
(* 需要显式类型注解来启用多态递归 *)
let rec (map_tree : ('a -> 'b) -> 'a tree -> 'b tree) = fun f -> function
| Leaf x -> Leaf (f x)
| Node children -> Node (List.map (map_tree f) children)
⚠️ 注意点:多态递归需要显式类型注解,因为 OCaml 的类型推导不能自动推导多态递归的类型。如果不加注解,类型变量会被固定为单一类型。
7. Hindley-Milner 类型系统简介
7.1 核心概念
| 概念 | 说明 |
|---|---|
| Type Variable | 类型变量 'a |
| Type Scheme | 类型方案 ∀'a. 'a -> 'a |
| Instantiation | 实例化:将类型方案中的变量替换为具体类型 |
| Generalization | 泛化:从具体类型抽象出类型方案 |
| Unification | 统一化:求解类型方程 |
7.2 Algorithm W 概要
Algorithm W(Γ, e):
根据 e 的形状:
- 变量 x: 查 Γ 中 x 的类型,实例化
- lambda \x.e: 新建类型变量 tv_x, 推导 e 的类型
- 应用 e1 e2: 推导 e1 和 e2 的类型,统一 e1 的参数类型与 e2 的类型
- let x = e1 in e2: 推导 e1,泛化其类型,推导 e2
7.3 统一化示例
(* 推导 fun x -> x + 1 *)
(* Step 1: x : 'a *)
(* Step 2: (+) : int -> int -> int *)
(* Step 3: 统一 'a 与 int *)
(* 结果: int -> int *)
8. 多态与模块系统
8.1 函子中的多态
module type Printable = sig
type t
val to_string : t -> string
end
module PrintUtils (P : Printable) = struct
let print_list xs =
List.iter (fun x -> print_endline (P.to_string x)) xs
let print_option = function
| None -> print_endline "None"
| Some x -> print_endline (P.to_string x)
end
8.2 显式多态注解
(* 在 .mli 文件中声明多态类型 *)
val concat_map : ('a -> 'b list) -> 'a list -> 'b list
(* 在 .ml 文件中实现 *)
let concat_map f xs = List.concat (List.map f xs)
9. 业务场景
9.1 通用序列化框架
type 'a serializer = {
to_json : 'a -> string;
of_json : string -> 'a option;
}
let int_serializer = {
to_json = string_of_int;
of_json = (fun s -> try Some (int_of_string s) with _ -> None);
}
let list_serializer s = {
to_json = fun xs ->
"[" ^ String.concat "," (List.map s.to_json xs) ^ "]";
of_json = fun _s -> failwith "not implemented";
}
9.2 类型安全的配置读取
type 'a config_entry = {
key : string;
default : 'a;
parse : string -> 'a option;
}
let string_entry key default = {
key; default;
parse = (fun s -> Some s);
}
let int_entry key default = {
key; default;
parse = (fun s -> try Some (int_of_string s) with _ -> None);
}
10. 扩展阅读
| 资源 | 链接 / 说明 |
|---|---|
| OCaml Manual: Polymorphism | ocaml.org/api |
| Real World OCaml: 类型推导 | Chapter on Type Inference |
| “Types and Programming Languages” | Benjamin C. Pierce, Chapter 22 |
| Value Restriction 详解 | ocaml.org/docs/faq |
| Let-Polymorphism 论文 | Damas & Milner (1982) |
💡 提示:在实际开发中,大多数时候 OCaml 的类型推导会自动工作。当你遇到 '_weak 类型或类型不匹配错误时,添加显式类型注解通常是最直接的解决方案。