文章目录

Haskell 模式匹配:case 语句与 guards

发布于 2026-04-02 06:04:07 · 浏览 12 次 · 评论 0 条

Haskell 模式匹配:case 语句与 guards

Haskell 中的模式匹配是处理数据结构的核心机制。它让你直接根据值的“形状”来编写逻辑,避免繁琐的条件判断。case 语句和 guards(守卫)是两种常用方式,各自适用于不同场景。


case 语句进行精确结构匹配

使用 case 语句对一个表达式进行多分支匹配,每个分支对应一种可能的数据构造形式。

  1. 写出 case 表达式的基本结构:以 case 开头,后接要匹配的表达式,然后是 of,再列出各个模式分支。
  2. 每个分支以模式开头,后跟箭头 -> 和对应的处理代码。
  3. 确保覆盖所有可能情况,否则运行时可能抛出“非穷尽模式”错误。

例如,处理一个自定义的 Maybe Int 类型:

describeMaybe :: Maybe Int -> String
describeMaybe x = case x of
  Nothing -> "没有值"
  Just n  -> "值是 " ++ show n

这里,x 被匹配为 NothingJust n。如果是 Just n,变量 n 自动绑定到内部的整数值。

你也可以在函数参数中直接使用模式匹配,这本质上是 case 的语法糖:

describeMaybe' :: Maybe Int -> String
describeMaybe' Nothing  = "没有值"
describeMaybe' (Just n) = "值是 " ++ show n

两者效果完全相同,但 case 更灵活——它可以在函数体任意位置对任意表达式进行匹配,而不仅限于参数。


用 guards 进行条件判断

使用 guards 在函数定义中基于布尔条件选择分支,适合需要比较、范围判断或复杂谓词的场景。

  1. 在函数等号右侧写竖线 |,后面紧跟一个布尔表达式(guard 条件)。
  2. 每个 guard 条件后跟等号和结果表达式
  3. 可添加 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 不能(只能使用已有变量)
依赖值的“形状”还是“值” 形状(构造器) 值(通过比较或函数计算)
典型场景 处理 MaybeEither、自定义类型、列表头尾 数值范围、相等性、自定义谓词函数

例如,处理列表时:

  • 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)。

这种组合既清晰又高效,充分利用了两种机制的优势。


避免常见错误

  1. 不要在 guards 中尝试模式匹配
    错误写法:

    f x | Just y <- x = y  -- 这不是标准 guards 语法!

    正确做法:改用 case 或在函数参数中匹配。

  2. 不要遗漏 case 的分支
    编译时开启 -Wall 警告,Haskell 会提示非穷尽模式。

  3. 不要滥用 lengthnull 判断列表
    优先使用 case xs of [] -> ...; (y:ys) -> ...,避免不必要的遍历。

  4. 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

通过分层匹配和条件判断,代码逻辑清晰且类型安全。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文