Elixir 模式匹配:= 运算符与 case
Elixir 的核心特性之一是模式匹配,它贯穿于变量赋值、函数定义、控制流等几乎所有语言结构中。理解 = 运算符和 case 表达式的模式匹配机制,是写出地道 Elixir 代码的关键。
理解 = 不是赋值,而是匹配
在 Elixir 中,= 是模式匹配运算符,不是传统意义上的“赋值”。它的行为是:尝试让左侧的模式与右侧的值匹配成功;如果匹配失败,则抛出 MatchError。
-
执行
x = 1。
此时,Elixir 发现左侧x是一个未绑定的变量(即“占位符”),于是将1绑定给x。结果:x的值为1。 -
执行
1 = x。
左侧是字面量1,右侧是已绑定变量x(值为1)。Elixir 检查1 == x是否成立。成立,匹配成功,无错误。 -
执行
2 = x。
左侧是2,右侧x仍为1。2 == 1不成立,抛出MatchError。
这种机制允许你用同一个符号完成“赋值”和“断言”两种操作,具体行为取决于左侧是否包含未绑定变量。
在复杂数据结构中使用模式匹配
模式匹配在处理元组、列表、映射等结构时尤为强大。
元组匹配
point = {1, 2, 3}
{x, y, z} = point
执行上述代码后,x 绑定为 1,y 为 2,z 为 3。如果左侧元组元素数量或结构不匹配右侧,会报错:
{a, b} = point # 错误!左侧2个元素,右侧3个
列表匹配
列表可通过 [head | tail] 语法拆分:
list = [1, 2, 3]
[first | rest] = list
执行后,first 为 1,rest 为 [2, 3]。你也可以匹配固定长度的列表:
[a, b, c] = list # a=1, b=2, c=3
但若列表长度不符,匹配失败:
[a, b] = list # MatchError
映射匹配
映射(Map)匹配只需左侧包含右侧的部分键即可成功:
person = %{name: "Alice", age: 30, city: "Paris"}
%{name: n, age: a} = person
执行后,n 为 "Alice",a 为 30。注意:左侧不需要包含所有键。但如果左侧指定的键在右侧不存在,则失败:
%{name: n, salary: s} = person # MatchError,因为 person 没有 :salary 键
使用 case 进行条件分支匹配
当需要根据值的不同结构执行不同逻辑时,case 表达式是最清晰的选择。它对表达式的结果依次尝试与每个子句的模式匹配,一旦成功就执行对应代码块。
基本用法
result = case {1, 2, 3} do
{a, b} -> "Two elements"
{a, b, c} -> "Three elements: #{a}, #{b}, #{c}"
_ -> "Other"
end
执行后,result 为 "Three elements: 1, 2, 3"。case 从上到下检查模式,第一个匹配成功的子句被执行,其余被忽略。
匹配并附加守卫条件
可在模式后添加 when 守卫,进一步限制匹配条件:
value = 5
case value do
x when x > 10 -> "Greater than 10"
x when x > 0 -> "Positive but <= 10"
_ -> "Zero or negative"
end
执行后,返回 "Positive but <= 10"。守卫中的表达式必须是可快速求值的布尔表达式,不能包含函数调用(除少数允许的如 is_integer/1 等)。
处理可能失败的操作
case 常用于处理返回 {:ok, result} 或 {:error, reason} 的函数:
file_result = File.read("example.txt")
case file_result do
{:ok, content} -> "File content: #{content}"
{:error, reason} -> "Failed to read file: #{reason}"
end
执行该代码会根据文件是否存在返回相应消息。这是 Elixir 中处理错误的标准方式,避免了异常流程控制。
避免常见陷阱
-
不要混淆 = 和 ==
=用于模式匹配,==用于相等比较。例如:{1, 2} = {1, 2} # 成功(结构相同) {1, 2} == {1, 2} # 返回 true(值相等) -
变量在模式中总是绑定,不会比较已有值
在case或函数参数中,左侧出现的变量名会被视为新绑定,即使同名变量已在外部存在:x = 1 case 2 do x -> "This will match, and x inside is 2, not 1!" end如果你想比较而非绑定,需使用
^引用外部变量:x = 1 case 2 do ^x -> "Won't match" _ -> "Matches because 2 != 1" end -
映射匹配不要求完全一致,但列表和元组要求
%{a: 1}能匹配%{a: 1, b: 2},但[1]不能匹配[1, 2]。
实战:解析 API 响应
假设你从 API 收到如下响应:
response = %{
status: "success",
data: %{
user: %{id: 123, name: "Bob"},
timestamp: 1717020000
}
}
你想安全提取用户 ID 和名称:
case response do
%{status: "success", data: %{user: %{id: id, name: name}}} ->
"User #{name} (ID: #{id}) fetched successfully."
%{status: "error", message: msg} ->
"Error: #{msg}"
_ ->
"Unexpected response format"
end
执行该 case 表达式,会精确匹配嵌套结构,并绑定所需字段。任何结构偏差都会进入 _ 默认分支,避免程序崩溃。
总结关键规则
| 场景 | 行为 |
|---|---|
| 左侧含未绑定变量 | 变量被绑定为右侧对应部分的值 |
| 左侧为字面量或已绑定变量 | 与右侧对应部分进行相等性检查 |
case 中的变量 |
默认视为新绑定(使用 ^ 引用外部变量) |
| 映射模式 | 只需包含目标映射的子集即可匹配 |
| 列表/元组模式 | 必须与目标结构完全一致(长度、嵌套) |

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