Haskell 模式匹配:case 语句与 guards
Haskell 中的模式匹配是处理数据结构的核心机制。它让你直接根据值的“形状”来编写逻辑,避免繁琐的条件判断。case 语句和 guards(守卫)是两种常用方式,各自适用于不同场景。
用 case 语句进行精确结构匹配
使用 case 语句对一个表达式进行多分支匹配,每个分支对应一种可能的数据构造形式。
- 写出
case表达式的基本结构:以case开头,后接要匹配的表达式,然后是of,再列出各个模式分支。 - 每个分支以模式开头,后跟箭头
->和对应的处理代码。 - 确保覆盖所有可能情况,否则运行时可能抛出“非穷尽模式”错误。
例如,处理一个自定义的 Maybe Int 类型:
describeMaybe :: Maybe Int -> String
describeMaybe x = case x of
Nothing -> "没有值"
Just n -> "值是 " ++ show n
这里,x 被匹配为 Nothing 或 Just n。如果是 Just n,变量 n 自动绑定到内部的整数值。
你也可以在函数参数中直接使用模式匹配,这本质上是 case 的语法糖:
describeMaybe' :: Maybe Int -> String
describeMaybe' Nothing = "没有值"
describeMaybe' (Just n) = "值是 " ++ show n
两者效果完全相同,但 case 更灵活——它可以在函数体任意位置对任意表达式进行匹配,而不仅限于参数。
用 guards 进行条件判断
使用 guards 在函数定义中基于布尔条件选择分支,适合需要比较、范围判断或复杂谓词的场景。
- 在函数等号右侧写竖线
|,后面紧跟一个布尔表达式(guard 条件)。 - 每个 guard 条件后跟等号和结果表达式。
- 可添加
otherwise作为兜底分支,它恒等于True。
例如,根据整数大小返回不同描述:
classifyNumber :: Int -> String
classifyNumber n
| n < 0 = "负数"
| n == 0 = "零"
| n <= 10 = "小正数"
| otherwise = "大正数"
注意:guards 不进行结构解构。你无法像 case 那样从 Just 5 中提取出 5;你只能测试 n 是否满足某个条件。
但你可以结合模式匹配和 guards。先用模式匹配解构数据,再用 guards 判断内容:
processMaybe :: Maybe Int -> String
processMaybe (Just n)
| n < 0 = "负值"
| n == 0 = "零值"
| otherwise = "正值"
processMaybe Nothing = "无值"
这里,(Just n) 是模式匹配,n < 0 等是 guards 条件。
何时用 case,何时用 guards?
选择取决于你要解决的问题类型。以下是关键区别:
| 特性 | case 语句 |
guards |
|---|---|---|
| 主要用途 | 解构数据结构(如列表、代数数据类型) | 基于布尔条件分支 |
| 能否绑定变量 | 能(如 Just x 中的 x) |
不能(只能使用已有变量) |
| 依赖值的“形状”还是“值” | 形状(构造器) | 值(通过比较或函数计算) |
| 典型场景 | 处理 Maybe、Either、自定义类型、列表头尾 |
数值范围、相等性、自定义谓词函数 |
例如,处理列表时:
-
用
case提取头部和尾部:headOrZero :: [Int] -> Int headOrZero xs = case xs of [] -> 0 (y:_) -> y -
用 guards 判断长度(不推荐,效率低):
headOrZeroBad :: [Int] -> Int headOrZeroBad xs | length xs == 0 = 0 | otherwise = head xs这种写法会遍历整个列表计算长度,而
case只需检查第一个构造器,常数时间完成。
组合使用:最佳实践
在实际代码中,经常需要同时使用两者。
先用模式匹配解构数据,再用 guards 对解构出的值做条件判断。
例如,安全地除两个 Maybe Int:
safeDivide :: Maybe Int -> Maybe Int -> Maybe Int
safeDivide mx my = case (mx, my) of
(Just x, Just y)
| y /= 0 -> Just (x `div` y)
| otherwise -> Nothing
_ -> Nothing
这里:
case (mx, my) of同时匹配两个Maybe值。- 只有当两者都是
Just时,才进入 guards 分支。 y /= 0防止除零错误。_通配符处理其余所有情况(任一为Nothing)。
这种组合既清晰又高效,充分利用了两种机制的优势。
避免常见错误
-
不要在 guards 中尝试模式匹配:
错误写法:f x | Just y <- x = y -- 这不是标准 guards 语法!正确做法:改用
case或在函数参数中匹配。 -
不要遗漏
case的分支:
编译时开启-Wall警告,Haskell 会提示非穷尽模式。 -
不要滥用
length或null判断列表:
优先使用case xs of [] -> ...; (y:ys) -> ...,避免不必要的遍历。 -
guards 的顺序很重要:
Haskell 从上到下检查 guards,第一个为True的分支生效。把最具体的条件放前面。
实战示例:解析命令行参数
假设你需要解析一个字符串列表,格式为 ["--port", "8080"] 或 ["--help"]。
parseArgs :: [String] -> Either String Int
parseArgs args = case args of
["--port", portStr] ->
case reads portStr of
[(port, "")]
| port > 0 && port < 65536 -> Right port
| otherwise -> Left "端口必须在 1-65535 之间"
_ -> Left ("无法解析端口号: " ++ portStr)
["--help"] -> Left "帮助信息..."
_ -> Left "未知参数"
这个例子展示了:
- 外层
case匹配参数列表的结构。 - 内层
case使用reads安全解析字符串为数字。 - guards 验证端口范围。
- 所有错误路径统一返回
Left,成功返回Right。
通过分层匹配和条件判断,代码逻辑清晰且类型安全。

暂无评论,快来抢沙发吧!