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

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 variantsv2.ocaml.org
Jacques Garrigue: “Code reuse through polymorphic variants”2000 年论文
Real World OCaml: Polymorphic VariantsChapter 6
“Polymorphic Variants in OCaml”Jane Street Tech Blog

💡 提示:多态变体最强大的应用场景是错误处理和跨模块类型组合。结合 Result 类型,可以构建类型安全且可扩展的错误处理链。