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

函数式编程艺术 / 06 模式匹配

06 模式匹配

“模式匹配是函数式编程中最接近魔法的特性——它让你直接描述数据的形状,然后按形状分派逻辑。”


6.1 模式匹配概述

模式匹配(Pattern Matching) 是一种根据数据的结构进行解构和分支的强大机制。它远比传统的 if-else 和 switch 更具表达力。

6.1.1 模式匹配 vs 传统分支

特性switch/case模式匹配
解构数据❌ 仅值比较✅ 解构嵌套数据
绑定变量✅ 匹配时绑定
穷尽性检查✅ 编译时检查
嵌套匹配✅ 多层嵌套
守卫条件✅ where/guard
可读性一般

6.2 基本模式匹配

6.2.1 值匹配

Haskell:

dayType :: String -> String
dayType "Saturday" = "Weekend"
dayType "Sunday"   = "Weekend"
dayType "Monday"   = "Start of work week"
dayType _          = "Weekday"  -- _ 是通配符

-- 更推荐用代数数据类型
data Day = Mon | Tue | Wed | Thu | Fri | Sat | Sun

dayType :: Day -> String
dayType Sat = "Weekend"
dayType Sun = "Weekend"
dayType Mon = "Start of work week"
dayType _   = "Weekday"

Rust:

fn day_type(day: &str) -> &str {
    match day {
        "Saturday" | "Sunday" => "Weekend",
        "Monday" => "Start of work week",
        _ => "Weekday",
    }
}

Python(3.10+ 结构化模式匹配):

def day_type(day: str) -> str:
    match day:
        case "Saturday" | "Sunday":
            return "Weekend"
        case "Monday":
            return "Start of work week"
        case _:
            return "Weekday"

Clojure:

;; 使用 core.match
(require '[clojure.core.match :refer [match]])

(defn day-type [day]
  (match [day]
    ["Saturday"] "Weekend"
    ["Sunday"]   "Weekend"
    ["Monday"]   "Start of work week"
    :else        "Weekday"))

6.2.2 类型匹配

Haskell:

data Shape = Circle Double
           | Rectangle Double Double
           | Triangle Double Double Double

area :: Shape -> Double
area (Circle r)        = pi * r * r
area (Rectangle w h)   = w * h
area (Triangle a b c)  = let s = (a + b + c) / 2
                          in sqrt (s * (s-a) * (s-b) * (s-c))

Rust:

enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
    Triangle(f64, f64, f64),
}

fn area(shape: &Shape) -> f64 {
    match shape {
        Shape::Circle(r) => std::f64::consts::PI * r * r,
        Shape::Rectangle(w, h) => w * h,
        Shape::Triangle(a, b, c) => {
            let s = (a + b + c) / 2.0;
            (s * (s - a) * (s - b) * (s - c)).sqrt()
        }
    }
}

Python:

from dataclasses import dataclass
from typing import Union
import math

@dataclass
class Circle:
    radius: float

@dataclass
class Rectangle:
    width: float
    height: float

Shape = Union[Circle, Rectangle]

def area(shape: Shape) -> float:
    match shape:
        case Circle(r):
            return math.pi * r * r
        case Rectangle(w, h):
            return w * h

6.3 解构(Destructuring)

解构是模式匹配的简化形式,用于从数据结构中提取值。

6.3.1 元组解构

Haskell:

-- 元组解构
fst' :: (a, b) -> a
fst' (x, _) = x

snd' :: (a, b) -> b
snd' (_, y) = y

-- 嵌套解构
nested :: ((Int, Int), (Int, Int)) -> Int
nested ((a, b), (c, d)) = a + b + c + d

JavaScript:

// 数组解构
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first=1, second=2, rest=[3,4,5]

// 嵌套解构
const { user: { name, address: { city } } } = data;
// name = data.user.name
// city = data.user.address.city

// 函数参数解构
const greet = ({ name, age }) => `Hello ${name}, you are ${age}`;

// 交换变量
let a = 1, b = 2;
[a, b] = [b, a];  // a=2, b=1

Python:

# 元组解构
first, *rest = [1, 2, 3, 4, 5]
# first=1, rest=[2,3,4,5]

# 嵌套解构
(a, b), (c, d) = (1, 2), (3, 4)

# 字典解构
def greet(user):
    name, age = user['name'], user['age']
    return f"Hello {name}, you are {age}"

# match-case 解构
def process(data):
    match data:
        case {'type': 'user', 'name': str(name), 'age': int(age)}:
            return f"User {name}, age {age}"
        case {'type': 'product', 'title': str(title)}:
            return f"Product {title}"

Rust:

// 元组解构
let (x, y, z) = (1, 2, 3);

// 结构体解构
struct Point { x: i32, y: i32 }
let Point { x, y } = Point { x: 1, y: 2 };

// 枚举解构
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
}

fn process(msg: Message) {
    match msg {
        Message::Quit => println!("Quit"),
        Message::Move { x, y } => println!("Move to ({}, {})", x, y),
        Message::Write(text) => println!("Text: {}", text),
    }
}

// if let 简化匹配
if let Some(value) = some_option {
    println!("Got: {}", value);
}

Clojure:

;; 向量解构
(let [[first second & rest] [1 2 3 4 5]]
  [first second rest])
;; [1 2 (3 4 5)]

;; map 解构
(let [{:keys [name age]} {:name "Alice" :age 30}]
  (str name " is " age " years old"))

;; 嵌套解构
(let [{:keys [name address]} {:name "Alice" :address {:city "Beijing"}}]
  (str name " lives in " (:city address)))

6.4 代数数据类型(Algebraic Data Types, ADT)

代数数据类型是函数式编程中构建数据模型的核心工具。

6.4.1 两种 ADT

类型英文构造方式示例
积类型Product Type所有字段同时存在data User = User String Int
和类型Sum Type多种可能取其一data Bool = True | False

6.4.2 积类型(Product Type)

-- Haskell
data User = User
  { userName  :: String
  , userAge   :: Int
  , userEmail :: String
  }

-- User 的值 = String × Int × String 的所有可能组合
-- 这就是"积"类型名字的来源
// Rust
struct User {
    name: String,
    age: u32,
    email: String,
}

6.4.3 和类型(Sum Type)

-- Haskell
data PaymentMethod
  = CreditCard String String String   -- cardNumber, expiry, cvv
  | PayPal String                     -- email
  | Bitcoin String                    -- walletAddress
  | Cash

-- PaymentMethod 的值 = CreditCard 所有可能 | PayPal 所有可能 | ...
-- 这就是"和"类型名字的来源

-- 更多和类型示例
data Maybe a = Nothing | Just a
data Either a b = Left a | Right b
data Ordering = LT | EQ | GT
data List a = Nil | Cons a (List a)
// Rust
enum PaymentMethod {
    CreditCard { number: String, expiry: String, cvv: String },
    PayPal(String),
    Bitcoin(String),
    Cash,
}

// Rust 枚举比 C/Java 枚举强大得多——它就是和类型
enum Result<T, E> {
    Ok(T),
    Err(E),
}

enum Option<T> {
    Some(T),
    None,
}
# Python 使用 Union type + dataclass
from dataclasses import dataclass
from typing import Union

@dataclass
class CreditCard:
    number: str
    expiry: str
    cvv: str

@dataclass
class PayPal:
    email: str

@dataclass
class Bitcoin:
    wallet_address: str

PaymentMethod = Union[CreditCard, PayPal, Bitcoin, None]

6.4.4 ADT 的代数性质

类型计数公式示例
积类型|A × B| = |A| × |B|(Bool, Bool) 有 4 个值
和类型|A + B| = |A| + |B|Maybe Bool 有 3 个值
函数类型|A → B| = |B|^|A|Bool → Bool 有 4 个函数
-- Bool × Bool 有 4 个值
(True, True), (True, False), (False, True), (False, False)

-- Maybe Bool 有 3 个值
Nothing, Just True, Just False

-- Bool → Bool 有 4 个函数
-- f1: True→True,  False→True
-- f2: True→True,  False→False
-- f3: True→False, False→True
-- f4: True→False, False→False

6.5 穷尽性检查(Exhaustiveness Check)

穷尽性检查确保模式匹配覆盖了所有可能的情况。

6.5.1 编译时保障

Haskell:

data Color = Red | Green | Blue

-- 编译器会警告不完整的模式匹配
showColor :: Color -> String
showColor Red   = "Red"
showColor Green = "Green"
-- Warning: Pattern match(es) are non-exhaustive
-- In an equation for 'showColor': Patterns not matched: Blue

-- 正确的穷尽匹配
showColor :: Color -> String
showColor Red   = "Red"
showColor Green = "Green"
showColor Blue  = "Blue"

Rust:

enum Color { Red, Green, Blue }

fn show(color: Color) -> &'static str {
    match color {
        Color::Red => "Red",
        Color::Green => "Green",
        // 如果省略 Blue,编译器报错:
        // non-exhaustive patterns: `Blue` not covered
        Color::Blue => "Blue",
    }
}

// 使用通配符(不推荐,失去穷尽性检查)
fn show_limited(color: Color) -> &'static str {
    match color {
        Color::Red => "Red",
        _ => "Other",
    }
}

6.5.2 嵌套穷尽性

// Rust 确保嵌套模式也是穷尽的
enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
}

enum Color {
    Red,
    Blue,
}

struct ColoredShape {
    color: Color,
    shape: Shape,
}

fn describe(cs: ColoredShape) -> String {
    match (cs.color, cs.shape) {
        (Color::Red, Shape::Circle(r)) => format!("Red circle, r={}", r),
        (Color::Red, Shape::Rectangle(w, h)) => format!("Red rect, {}x{}", w, h),
        (Color::Blue, Shape::Circle(r)) => format!("Blue circle, r={}", r),
        (Color::Blue, Shape::Rectangle(w, h)) => format!("Blue rect, {}x{}", w, h),
    }
}

6.6 守卫(Guard)

守卫为模式匹配提供额外的条件约束。

6.6.1 Haskell 中的守卫

-- 基本守卫
bmiCategory :: Double -> String
bmiCategory bmi
  | bmi < 18.5 = "Underweight"
  | bmi < 25.0 = "Normal"
  | bmi < 30.0 = "Overweight"
  | otherwise   = "Obese"

-- 模式 + 守卫
describeAge :: Int -> String
describeAge age
  | age < 0   = "Invalid"
  | age < 13  = "Child"
  | age < 18  = "Teenager"
  | age < 65  = "Adult"
  | otherwise = "Senior"

-- 复杂示例:分类三角形
classifyTriangle :: Int -> Int -> Int -> String
classifyTriangle a b c
  | a + b <= c || a + c <= b || b + c <= a = "Not a triangle"
  | a == b && b == c = "Equilateral"
  | a == b || b == c || a == c = "Isosceles"
  | otherwise = "Scalene"

Rust:

fn bmi_category(bmi: f64) -> &'static str {
    match bmi {
        bmi if bmi < 18.5 => "Underweight",
        bmi if bmi < 25.0 => "Normal",
        bmi if bmi < 30.0 => "Overweight",
        _ => "Obese",
    }
}

fn classify_triangle(a: i32, b: i32, c: i32) -> &'static str {
    match (a, b, c) {
        _ if a + b <= c || a + c <= b || b + c <= a => "Not a triangle",
        _ if a == b && b == c => "Equilateral",
        _ if a == b || b == c || a == c => "Isosceles",
        _ => "Scalene",
    }
}

Python:

def bmi_category(bmi: float) -> str:
    match bmi:
        case b if b < 18.5: return "Underweight"
        case b if b < 25.0: return "Normal"
        case b if b < 30.0: return "Overweight"
        case _: return "Obese"

6.7 嵌套模式匹配

6.7.1 深层嵌套解构

Haskell:

data Address = Address { city :: String, zipCode :: String }
data Company = Company { companyName :: String, address :: Address }
data Employee = Employee
  { empName    :: String
  , company    :: Company
  , role       :: String
  }

-- 嵌套模式匹配
getWorkCity :: Employee -> String
getWorkCity (Employee _ (Company _ (Address city _)) _) = city

-- 更可读的 Record 语法
getWorkCity' :: Employee -> String
getWorkCity' Employee { company = Company { address = Address { city = c } } } = c

JavaScript:

// 深层解构
const {
  name: empName,
  company: {
    name: companyName,
    address: { city }
  },
  role
} = employee;

// 函数参数深层解构
const getWorkCity = ({ company: { address: { city } } }) => city;

Python:

def get_work_city(employee: dict) -> str:
    match employee:
        case {'company': {'address': {'city': str(city)}}}:
            return city
        case _:
            return "Unknown"

6.8 业务场景

6.8.1 AST 解释器

-- 用代数数据类型表示算术表达式
data Expr
  = Num Double
  | Add Expr Expr
  | Mul Expr Expr
  | Neg Expr
  | Var String

-- 求值
eval :: Map.Map String Double -> Expr -> Double
eval _   (Num x)     = x
eval env (Add a b)   = eval env a + eval env b
eval env (Mul a b)   = eval env a * eval env b
eval env (Neg e)     = negate (eval env e)
eval env (Var name)  = case Map.lookup name env of
  Just v  -> v
  Nothing -> error $ "Undefined variable: " ++ name

-- 化简
simplify :: Expr -> Expr
simplify (Add (Num 0) e) = simplify e
simplify (Add e (Num 0)) = simplify e
simplify (Mul (Num 1) e) = simplify e
simplify (Mul e (Num 1)) = simplify e
simplify (Mul (Num 0) _) = Num 0
simplify (Neg (Neg e))   = simplify e
simplify (Add a b)       = Add (simplify a) (simplify b)
simplify (Mul a b)       = Mul (simplify a) (simplify b)
simplify e               = e

Rust:

#[derive(Debug, Clone)]
enum Expr {
    Num(f64),
    Add(Box<Expr>, Box<Expr>),
    Mul(Box<Expr>, Box<Expr>),
    Neg(Box<Expr>),
    Var(String),
}

impl Expr {
    fn eval(&self, env: &std::collections::HashMap<String, f64>) -> f64 {
        match self {
            Expr::Num(x) => *x,
            Expr::Add(a, b) => a.eval(env) + b.eval(env),
            Expr::Mul(a, b) => a.eval(env) * b.eval(env),
            Expr::Neg(e) => -e.eval(env),
            Expr::Var(name) => *env.get(name).expect(&format!("Undefined: {}", name)),
        }
    }

    fn simplify(&self) -> Expr {
        match self {
            Expr::Add(a, b) => match (a.simplify(), b.simplify()) {
                (Expr::Num(0.0), e) | (e, Expr::Num(0.0)) => e,
                (a, b) => Expr::Add(Box::new(a), Box::new(b)),
            },
            // ... 其他简化规则
            _ => self.clone(),
        }
    }
}

6.8.2 状态机

-- 用和类型表示订单状态
data OrderStatus
  = Pending
  | Paid Double
  | Shipped String String   -- carrier, trackingNumber
  | Delivered String         -- signature
  | Cancelled String         -- reason

nextStatus :: OrderStatus -> String -> OrderStatus
nextStatus Pending "pay"      = Paid 0.0
nextStatus (Paid _) "ship"    = Shipped "" ""
nextStatus (Shipped _ _) "deliver" = Delivered ""
nextStatus _ "cancel"         = Cancelled "User cancelled"
nextStatus status _           = status  -- 无效转换,保持原状态

6.9 注意事项

注意事项说明
通配符使用避免过多 _ 导致丢失穷尽性检查的收益
模式顺序模式从上到下匹配,第一个匹配的被执行
性能编译器通常将模式匹配优化为跳转表
嵌套深度过深嵌套可读性差,考虑拆分函数
守卫副作用守卫应该是纯函数

6.10 小结

要点说明
模式匹配根据数据结构进行解构和分支
解构从数据结构中提取值的语法糖
代数数据类型积类型(AND)+ 和类型(OR)
穷尽性检查编译器确保覆盖所有情况
守卫模式匹配的条件约束

扩展阅读

  1. Algebraic Data Types - Haskell Wiki
  2. Pattern Matching - Rust Book
  3. Structural Pattern Matching - Python PEP 634
  4. 《Types and Programming Languages》 — Benjamin C. Pierce

下一章07 递归与不动点 — 函数式编程的循环替代方案