OCaml 教程 / 模式匹配
模式匹配
概述
模式匹配(Pattern Matching)是 OCaml 最强大和最常用的特性之一。它不仅是 switch 语句的增强版,更是一种结构化数据解构和条件分支的统一机制。编译器会对模式匹配进行穷尽性检查(exhaustiveness check),确保所有可能的情况都被处理。
match 表达式基础
(* 基本 match 语法 *)
let describe_number n =
match n with
| 0 -> "零"
| 1 -> "一"
| 2 -> "二"
| _ -> "其他"
let _ = describe_number 2 (* => "二" *)
let _ = describe_number 5 (* => "其他" *)
match 表达式由三部分组成:
match ... with— 指定要匹配的值| pattern -> expression— 模式分支- 返回第一个匹配分支的结果
⚠️ 注意:每个分支用 | 分隔(第一个 | 可选)。每个分支必须返回相同类型的值。
字面量匹配
(* 整数字面量 *)
let classify n =
match n with
| 0 -> "零"
| 1 -> "一"
| -1 -> "负一"
| _ -> "其他"
(* 字符串字面量 *)
let color_name c =
match c with
| "red" -> "红色"
| "green" -> "绿色"
| "blue" -> "蓝色"
| _ -> "未知颜色"
(* 字符字面量 *)
let is_vowel c =
match c with
| 'a' | 'e' | 'i' | 'o' | 'u' -> true
| 'A' | 'E' | 'I' | 'O' | 'U' -> true
| _ -> false
(* 布尔字面量 *)
let bool_to_string b =
match b with
| true -> "是"
| false -> "否"
💡 提示:使用 | 分隔多个模式可以实现 “或” 逻辑,匹配其中任意一个。
通配符 _
_ 匹配任何值且不绑定变量:
(* 忽略不关心的值 *)
let get_first (a, _, _) = a
let _ = get_first (1, "hello", true) (* => 1 *)
(* 忽略列表的尾部 *)
let head_or_default lst default =
match lst with
| x :: _ -> x
| [] -> default
let _ = head_or_default [1; 2; 3] 0 (* => 1 *)
let _ = head_or_default [] 0 (* => 0 *)
⚠️ 注意:虽然 _ 很方便,但在关键逻辑中应尽量使用具名变量,使代码更具自文档性。
when 守卫
when 守卫为模式添加额外条件:
(* 使用 when 添加条件 *)
let classify_age age =
match age with
| n when n < 0 -> "无效年龄"
| n when n < 13 -> "儿童"
| n when n < 18 -> "青少年"
| n when n < 65 -> "成年人"
| _ -> "老年人"
(* 列表匹配中的 when *)
let rec find_first_even lst =
match lst with
| [] -> None
| x :: _ when x mod 2 = 0 -> Some x
| _ :: rest -> find_first_even rest
let _ = find_first_even [1; 3; 4; 5; 6] (* => Some 4 *)
(* 实际应用:处理 HTTP 状态码 *)
let handle_status code =
match code with
| n when n >= 200 && n < 300 -> "成功"
| n when n >= 300 && n < 400 -> "重定向"
| n when n >= 400 && n < 500 -> "客户端错误"
| n when n >= 500 -> "服务器错误"
| _ -> "未知状态码"
let _ = handle_status 404 (* => "客户端错误" *)
⚠️ 注意:when 守卫会影响穷尽性检查。编译器可能无法验证带 when 分支的穷尽性,谨慎使用。
嵌套模式
模式可以嵌套,实现深层数据结构的解构:
(* 嵌套元组 *)
let describe_pair pair =
match pair with
| (0, 0) -> "原点"
| (0, _) -> "y 轴上"
| (_, 0) -> "x 轴上"
| (x, y) when x = y -> "对角线上"
| _ -> "普通点"
(* 嵌套列表 *)
let rec describe_list lst =
match lst with
| [] -> "空列表"
| [x] -> Printf.sprintf "只有一个元素: %d" x
| [x; y] -> Printf.sprintf "两个元素: %d 和 %d" x y
| x :: _ -> Printf.sprintf "首元素: %d,共多个元素" x
(* 深层嵌套 *)
type shape =
| Circle of float
| Rect of float * float
| Point
let describe_shape s =
match s with
| Circle r when r > 0.0 -> Printf.sprintf "圆,半径 %.2f" r
| Circle _ -> "无效圆"
| Rect (w, h) when w > 0.0 && h > 0.0 ->
Printf.sprintf "矩形 %.2fx%.2f (面积=%.2f)" w h (w *. h)
| Rect _ -> "无效矩形"
| Point -> "点"
元组匹配
(* 元组解构 *)
let (x, y) = (1, 2)
(* x = 1, y = 2 *)
(* 交换 *)
let swap (a, b) = (b, a)
(* 三元组 *)
let (a, b, c) = (1, "hello", true)
(* match 中的元组模式 *)
let classify_point point =
match point with
| (0, 0) -> "原点"
| (x, 0) -> Printf.sprintf "x=%d 轴上" x
| (0, y) -> Printf.sprintf "y=%d 轴上" y
| (x, y) -> Printf.sprintf "(%d, %d)" x y
(* 函数参数中的模式匹配 *)
let distance (x1, y1) (x2, y2) =
let dx = float_of_int (x2 - x1) in
let dy = float_of_int (y2 - y1) in
sqrt (dx *. dx +. dy *. dy)
let _ = distance (0, 0) (3, 4) (* => 5.0 *)
记录匹配
type person = {
name : string;
age : int;
email : string option;
}
let describe_person p =
match p with
| { name; age; email = Some e } ->
Printf.sprintf "%s (%d岁, %s)" name age e
| { name; age; email = None } ->
Printf.sprintf "%s (%d岁, 无邮箱)" name age
(* 部分匹配 — 只关心某些字段 *)
let is_adult p =
match p with
| { age; _ } -> age >= 18
(* 使用 record punning(字段名与变量名相同时可省略) *)
let get_name { name; _ } = name
💡 提示:在记录模式中使用 _ 来忽略不关心的字段。
变体匹配
变体(Variant)类型的模式匹配是 OCaml 最强大的特性之一:
type color = Red | Green | Blue | RGB of int * int * int
let color_to_hex c =
match c with
| Red -> "#FF0000"
| Green -> "#00FF00"
| Blue -> "#0000FF"
| RGB (r, g, b) -> Printf.sprintf "#%02X%02X%02X" r g b
(* 递归变体 — 表达式树 *)
type expr =
| Num of float
| Add of expr * expr
| Mul of expr * expr
| Neg of expr
let rec eval e =
match e with
| Num n -> n
| Add (a, b) -> eval a +. eval b
| Mul (a, b) -> eval a *. eval b
| Neg e -> ~-.(eval e)
let expression = Add (Num 1.0, Mul (Num 2.0, Num 3.0))
let _ = eval expression (* => 7.0 *)
(* 带守卫的变体匹配 *)
type response =
| Success of string
| Error of int * string
let handle_response r =
match r with
| Success body -> "成功: " ^ body
| Error (code, _) when code >= 500 -> "服务器错误,请稍后重试"
| Error (code, msg) -> Printf.sprintf "错误 %d: %s" code msg
异常匹配
try ... with 使用与 match 相同的模式匹配语法:
(* 基本异常处理 *)
let safe_divide a b =
try
Some (a / b)
with
| Division_by_zero -> None
let _ = safe_divide 10 3 (* => Some 3 *)
let _ = safe_divide 10 0 (* => None *)
(* 匹配多种异常 *)
let read_file_safe path =
try
let ic = open_in path in
let content = really_input_string ic (in_channel_length ic) in
close_in ic;
Ok content
with
| Sys_error msg -> Error ("文件错误: " ^ msg)
| End_of_file -> Error "意外的文件结束"
| e -> Error ("未知错误: " ^ Printexc.to_string e)
(* 自定义异常 *)
exception Parse_error of int * string
let parse_line line_num line =
match String.split_on_char ',' line with
| [name; age_str] ->
(name, int_of_string age_str)
| _ ->
raise (Parse_error (line_num, "格式错误: " ^ line))
let parse_csv content =
let lines = String.split_on_char '\n' content in
List.mapi (fun i line ->
try parse_line (i + 1) line
with
| Parse_error (n, msg) ->
Printf.eprintf "第 %d 行: %s\n" n msg;
("unknown", 0)
| Failure _ ->
Printf.eprintf "第 %d 行: 数字解析失败\n" (i + 1);
("unknown", 0)
) lines
⚠️ 注意:try ... with 中的 with 部分使用与 match 完全相同的模式匹配语法。
穷尽性检查
OCaml 编译器会检查模式匹配是否覆盖了所有可能的情况:
type direction = North | South | East | West
(* 完整匹配 — 编译通过 *)
let dir_to_string d =
match d with
| North -> "北"
| South -> "南"
| East -> "东"
| West -> "西"
(* 不完整匹配 — 编译器警告 *)
let dir_to_string_bad d =
match d with
| North -> "北"
| South -> "南"
(* 编译器发出警告: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
(East|West) *)
(* 使用通配符消除警告 *)
let dir_to_string_safe d =
match d with
| North -> "北"
| South -> "南"
| _ -> "其他方向"
or-patterns(或模式)
let is_cardinal d =
match d with
| North | South | East | West -> true
(* 列表中的或模式 *)
let is_special_char c =
match c with
| '!' | '@' | '#' | '$' | '%' -> true
| _ -> false
💡 提示:穷尽性检查是 OCaml 类型系统的一大安全特性。当修改变体类型(增加新的构造器)时,编译器会自动提醒你所有需要更新的匹配表达式,避免遗漏。
模式匹配编译优化
OCaml 编译器对模式匹配进行了多种优化:
决策树编译
编译器将模式匹配编译为高效的决策树,而不是简单的逐个比较:
(* 编译器可能优化为跳转表 *)
let int_to_roman n =
match n with
| 1 -> "I"
| 2 -> "II"
| 3 -> "III"
| 4 -> "IV"
| 5 -> "V"
| 6 -> "VI"
| 7 -> "VII"
| 8 -> "VIII"
| 9 -> "IX"
| _ -> "不支持"
(* 编译器可能生成类似 switch 的高效代码 *)
嵌套扁平化
(* 编译器会优化嵌套的模式匹配 *)
let classify lst =
match lst with
| [] -> "空"
| [x] when x > 0 -> "单个正数"
| [x] -> "单个非正数"
| _ -> "多个元素"
(* 编译器会将 when 条件优化到决策树中 *)
实用示例:JSON 解析器
(* 简化的 JSON AST *)
type json =
| JNull
| JBool of bool
| JNum of float
| JStr of string
| JArr of json list
| JObj of (string * json) list
(* JSON 值转字符串 *)
let rec json_to_string indent j =
let pad = String.make (indent * 2) ' ' in
match j with
| JNull -> "null"
| JBool b -> string_of_bool b
| JNum n ->
if Float.is_integer n then string_of_int (int_of_float n)
else Printf.sprintf "%.2f" n
| JStr s -> Printf.sprintf "\"%s\"" s
| JArr [] -> "[]"
| JArr items ->
let inner = List.map (fun v ->
pad ^ " " ^ json_to_string (indent + 1) v
) items in
"[\n" ^ String.concat ",\n" inner ^ "\n" ^ pad ^ "]"
| JObj [] -> "{}"
| JObj pairs ->
let inner = List.map (fun (k, v) ->
pad ^ " \"" ^ k ^ "\": " ^ json_to_string (indent + 1) v
) pairs in
"{\n" ^ String.concat ",\n" inner ^ "\n" ^ pad ^ "}"
(* 从 JSON 提取值 — 带错误处理 *)
let rec get_nested j keys =
match keys, j with
| [], _ -> Some j
| key :: rest, JObj pairs ->
(match List.assoc_opt key pairs with
| Some v -> get_nested v rest
| None -> None)
| _, _ -> None
(* 使用示例 *)
let () =
let data = JObj [
("name", JStr "Alice");
("age", JNum 30.0);
("scores", JArr [JNum 95.0; JNum 87.0; JNum 92.0]);
("address", JObj [
("city", JStr "北京");
("zip", JStr "100000")
]);
] in
print_endline (json_to_string 0 data);
match get_nested data ["address"; "city"] with
| Some (JStr city) -> Printf.printf "城市: %s\n" city
| _ -> print_endline "未找到"
业务场景
| 场景 | 模式匹配用法 |
|---|---|
| 状态机 | 对状态变体进行匹配 |
| 命令解析 | 匹配命令字符串 |
| 错误处理 | try ... with + 异常模式 |
| AST 遍历 | 递归匹配表达式树 |
| 配置解析 | 记录模式 + 默认值 |
| API 响应处理 | 变体匹配(Success/Error) |