OCaml 教程 / 多态变体
多态变体(Polymorphic Variants)
多态变体是 OCaml 中非常灵活的类型特性,它允许标签不需要预先声明就能使用,且可以通过类型推导自动进行子类型约束。
1. 基本语法
1.1 普通变体 vs 多态变体
(* 普通变体:必须先声明 *)
type color = Red | Green | Blue
let r : color = Red
(* 多态变体:以反引号 ` 开头,无需声明 *)
let r : [> `Red] = `Red
let g : [> `Green] = `Green
| 特性 | 普通变体 | 多态变体 |
|---|---|---|
| 声明要求 | 必须先 type 声明 | 直接使用 |
| 标签前缀 | 无 | 反引号 ` |
| 封闭性 | 封闭 | 可开放可封闭 |
| 类型推导 | 固定 | 自动推导子类型 |
| 模块边界 | 跨模块需要声明 | 跨模块直接使用 |
1.2 构造与匹配
(* 构造 *)
let ok = `Ok 42
let err = `Error "something went wrong"
(* 模式匹配 *)
let to_string = function
| `Red -> "red"
| `Green -> "green"
| `Blue -> "blue"
let () = print_endline (to_string `Red)
2. 类型推断
2.1 开放变体类型
(* [> `Red | `Green | `Blue] 表示"至少包含这三个标签" *)
let describe c =
match c with
| `Red -> "red"
| `Green -> "green"
| `Blue -> "blue"
| _ -> "unknown"
(* 函数类型: [> `Blue | `Green | `Red ] -> string *)
2.2 封闭变体类型
(* [< `Red | `Green | `Blue] 表示"最多包含这三个标签" *)
let is_warm : [< `Red | `Orange | `Yellow | `Green | `Blue ] -> bool =
function
| `Red | `Orange | `Yellow -> true
| `Green | `Blue -> false
2.3 变体类型约束总结
| 类型约束 | 含义 | 使用场景 |
|---|---|---|
[> Tag]` | 至少包含 Tag | 返回值、协变位置 |
[< Tag1 | Tag2] | 最多包含这些标签 | 参数、逆变位置 |
[Tag]` | 恰好只含 Tag | 精确类型 |
private [> Tag]` | 私有开放变体 | 模块实现细节 |
3. 子类型约束
(* 子类型关系: [> `A | `B] 是 [> `A | `B | `C] 的子类型 *)
(* 因为前者提供的标签更少,使用起来更安全 *)
type narrow = [ `Red | `Green ]
type wide = [ `Red | `Green | `Blue ]
(* 显式强制转换 *)
let narrow_red : narrow = `Red
let wide_red : wide = (narrow_red :> wide) (* OK *)
(* 逆方向不行 *)
(* let bad : narrow = (wide_red :> narrow) (* 编译错误! *) *)
⚠️ 注意点:多态变体的子类型转换使用 :> 运算符,与对象子类型相同。类型 [< A | B] 比 [< A | B | C]` 更宽(更通用),因为它可以接受更多位置的输入。
4. 多态变体与模块
4.1 模块间共享标签
(* 网络模块 *)
module Network = struct
type error = [ `Timeout | `Connection_refused | `Host_not_found ]
let fetch url : (string, error) result =
if url = "" then Error `Host_not_found
else Ok ("data from " ^ url)
end
(* 文件模块 *)
module FileIO = struct
type error = [ `File_not_found | `Permission_denied | `IO_error ]
let read path : (string, error) result =
if path = "" then Error `File_not_found
else Ok ("content of " ^ path)
end
(* 统一错误处理 *)
type app_error = [ Network.error | FileIO.error ]
let handle_error : app_error -> string = function
| `Timeout -> "network timeout"
| `Connection_refused -> "connection refused"
| `Host_not_found -> "host not found"
| `File_not_found -> "file not found"
| `Permission_denied -> "permission denied"
| `IO_error -> "IO error"
4.2 类型定义与签名
(* .mli 中声明封闭类型 *)
(* val handle : [< `Ok of string | `Error of string] -> unit *)
(* .ml 中实现 *)
let handle = function
| `Ok msg -> print_endline msg
| `Error msg -> prerr_endline msg
💡 提示:在 .mli 中使用封闭类型 [< ...] 可以隐藏实现细节,防止外部代码添加新的标签。
5. 多态变体与 GADTs
(* 使用多态变体定义表达式类型 *)
type 'a expr =
| Int : int -> int expr
| Bool : bool -> bool expr
(* 多态变体版本 *)
let eval_int : [> `Int of int] -> int = function
| `Int n -> n
| _ -> 0
(* 更灵活的方式 *)
type value = [
| `Int of int
| `Float of float
| `String of string
| `Bool of bool
]
let rec to_string : value -> string = function
| `Int n -> string_of_int n
| `Float f -> string_of_float f
| `String s -> "\"" ^ s ^ "\""
| `Bool b -> string_of_bool b
6. 设计模式
6.1 错误处理模式
(* 分层错误类型 *)
type parse_error = [ `Unexpected_eof | `Syntax_error of string ]
type type_error = [ `Type_mismatch | `Undefined_variable of string ]
type runtime_error = [ `Division_by_zero | `Stack_overflow ]
type all_error = [ parse_error | type_error | runtime_error ]
let format_error : all_error -> string = function
| `Unexpected_eof -> "unexpected end of file"
| `Syntax_error msg -> "syntax error: " ^ msg
| `Type_mismatch -> "type mismatch"
| `Undefined_variable name -> "undefined variable: " ^ name
| `Division_by_zero -> "division by zero"
| `Stack_overflow -> "stack overflow"
(* 返回结果的函数 *)
let parse_expr s : (int, parse_error) result =
if s = "" then Error `Unexpected_eof
else Ok (int_of_string s)
6.2 事件系统
type ui_event = [
| `Click of int * int
| `Key of char
| `Resize of int * int
| `Scroll of float
]
let handle_event : ui_event -> unit = function
| `Click (x, y) -> Printf.printf "Click at (%d, %d)\n" x y
| `Key c -> Printf.printf "Key: %c\n" c
| `Resize (w, h) -> Printf.printf "Resize to %dx%d\n" w h
| `Scroll delta -> Printf.printf "Scroll: %f\n" delta
(* 扩展事件 —— 不需要修改原有代码 *)
type extended_event = [ ui_event | `Focus | `Blur ]
let handle_extended : extended_event -> unit = function
| #ui_event as e -> handle_event e
| `Focus -> print_endline "Focus gained"
| `Blur -> print_endline "Focus lost"
6.3 命令行参数解析
type arg_spec = [
| `Flag of string * (unit -> unit)
| `String of string * (string -> unit)
| `Int of string * (int -> unit)
]
let parse_args specs args =
let len = Array.length args in
let i = ref 1 in
while !i < len do
let arg = args.(!i) in
match List.find_opt (function
| `Flag (name, _) | `String (name, _) | `Int (name, _) ->
name = arg
) specs with
| Some (`Flag (_, f)) ->
f (); incr i
| Some (`String (_, f)) ->
incr i; if !i < len then f args.(!i); incr i
| Some (`Int (_, f)) ->
incr i; if !i < len then f (int_of_string args.(!i)); incr i
| None ->
Printf.eprintf "Unknown arg: %s\n" arg; incr i
done
7. JSON 处理实例
type json = [
| `Null
| `Bool of bool
| `Int of int
| `Float of float
| `String of string
| `Array of json list
| `Object of (string * json) list
]
let rec to_json_string : json -> string = function
| `Null -> "null"
| `Bool b -> string_of_bool b
| `Int n -> string_of_int n
| `Float f -> string_of_float f
| `String s -> Printf.sprintf "%S" s
| `Array items ->
"[" ^ String.concat ", " (List.map to_json_string items) ^ "]"
| `Object pairs ->
let pair_to_s (k, v) =
Printf.sprintf "%S: %s" k (to_json_string v)
in
"{" ^ String.concat ", " (List.map pair_to_s pairs) ^ "}"
(* 构造 JSON *)
let user : json = `Object [
"name", `String "Alice";
"age", `Int 30;
"active", `Bool true;
"tags", `Array [`String "admin"; `String "user"];
]
let () = print_endline (to_json_string user)
8. 多态变体 vs 普通变体
| 考量 | 多态变体 | 普通变体 |
|---|---|---|
| 独立定义 | ✅ 无需声明 | ❌ 需要 type 声明 |
| 错误信息 | ⚠️ 较长且复杂 | ✅ 清晰简洁 |
| 性能 | ⚠️ 可能有额外开销 | ✅ 高效 |
| 模块封装 | ⚠️ 需要 .mli 约束 | ✅ 天然封装 |
| 可扩展性 | ✅ 开放递归 | ❌ 封闭 |
| 模式匹配穷举检查 | ⚠️ 依赖类型 | ✅ 完全可靠 |
| 跨模块组合 | ✅ 轻松合并 | ❌ 需要手动定义联合 |
选择建议
- 使用普通变体:类型是封闭的、需要严格模式匹配检查、性能关键
- 使用多态变体:错误类型组合、跨模块扩展、临时的标记类型、与其他库交互
⚠️ 注意点:多态变体的类型错误消息往往很长且难以阅读。当你看到包含大量 `Tag1 | `Tag2 | … 的错误消息时,考虑改用普通变体或添加显式类型注解。
9. 开放变体类型(Open Variant Types)
OCaml 4.02 引入了 private 类型,可用于实现开放变体:
(* 使用 private 类型实现受限扩展 *)
type expr = [
| `Int of int
| `Add of expr * expr
| `Mul of expr * expr
]
(* 扩展 *)
type ext_expr = [
| expr
| `Sub of ext_expr * ext_expr
| `Div of ext_expr * ext_expr
]
let rec eval_ext : ext_expr -> int = function
| `Int n -> n
| `Add (a, b) -> eval_ext a + eval_ext b
| `Mul (a, b) -> eval_ext a * eval_ext b
| `Sub (a, b) -> eval_ext a - eval_ext b
| `Div (a, b) -> eval_ext a / eval_ext b
10. 扩展阅读
| 资源 | 说明 |
|---|---|
| OCaml Manual: Polymorphic variants | v2.ocaml.org |
| Jacques Garrigue: “Code reuse through polymorphic variants” | 2000 年论文 |
| Real World OCaml: Polymorphic Variants | Chapter 6 |
| “Polymorphic Variants in OCaml” | Jane Street Tech Blog |
💡 提示:多态变体最强大的应用场景是错误处理和跨模块类型组合。结合 Result 类型,可以构建类型安全且可扩展的错误处理链。