名称: elixir-pattern-matching 用户可调用: false 描述: 当需要Elixir模式匹配时使用,包括函数子句、case语句、with语句和解构。用于优雅的控制流。 允许工具:
- Bash
- Read
Elixir 模式匹配
掌握Elixir中的模式匹配,编写优雅、声明式的代码。 此技能涵盖函数模式、case语句、守卫和解构各种数据结构。
基本模式匹配
# 简单赋值就是模式匹配
x = 1
1 = x # 这行得通,因为x匹配1
# 使用元组的模式匹配
{:ok, value} = {:ok, "success"}
value # => "success"
# 如果模式不匹配会引发MatchError
# {:error, _} = {:ok, "success"} # MatchError
# 使用pin操作符来使用现有值
x = 1
^x = 1 # 有效
# ^x = 2 # MatchError
# 用下划线忽略值
{:ok, _} = {:ok, "any value"}
{_, _, third} = {1, 2, 3}
third # => 3
函数模式匹配
defmodule Calculator do
def add(a, b), do: a + b
def factorial(0), do: 1
def factorial(n) when n > 0, do: n * factorial(n - 1)
def describe_tuple({:ok, value}) do
"Success: #{value}"
end
def describe_tuple({:error, reason}) do
"Error: #{reason}"
end
def describe_tuple(_) do
"Unknown tuple format"
end
end
# 使用
Calculator.factorial(5) # => 120
Calculator.describe_tuple({:ok, "done"}) # => "Success: done"
守卫在模式匹配中
defmodule NumberChecker do
def check(x) when is_integer(x) and x > 0 do
"Positive integer"
end
def check(x) when is_integer(x) and x < 0 do
"Negative integer"
end
def check(0), do: "Zero"
def check(x) when is_float(x), do: "Float"
def check(_), do: "Not a number"
end
defmodule Validator do
def valid_email?(email) when is_binary(email) do
String.contains?(email, "@")
end
def valid_email?(_), do: false
def in_range?(num, min, max)
when is_number(num) and num >= min and num <= max do
true
end
def in_range?(_, _, _), do: false
end
Case 语句
defmodule ResponseHandler do
def handle(response) do
case response do
{:ok, data} ->
{:success, data}
{:error, :not_found} ->
{:failure, "Resource not found"}
{:error, :timeout} ->
{:failure, "Request timed out"}
{:error, reason} ->
{:failure, "Error: #{inspect(reason)}"}
_ ->
{:failure, "Unknown response"}
end
end
def parse_number(str) do
case Integer.parse(str) do
{num, ""} -> {:ok, num}
{num, _remainder} -> {:ok, num}
:error -> {:error, "Not a valid number"}
end
end
end
With 语句用于管道模式匹配
defmodule UserService do
def create_user(params) do
with {:ok, email} <- validate_email(params["email"]),
{:ok, password} <- validate_password(params["password"]),
{:ok, user} <- insert_user(email, password),
{:ok, _} <- send_welcome_email(user) do
{:ok, user}
else
{:error, reason} -> {:error, reason}
_ -> {:error, "Unknown error"}
end
end
defp validate_email(email) when is_binary(email) do
if String.contains?(email, "@") do
{:ok, email}
else
{:error, "Invalid email"}
end
end
defp validate_email(_), do: {:error, "Email required"}
defp validate_password(pass) when is_binary(pass) do
if String.length(pass) >= 8 do
{:ok, pass}
else
{:error, "Password too short"}
end
end
defp validate_password(_), do: {:error, "Password required"}
defp insert_user(email, password) do
{:ok, %{id: 1, email: email}}
end
defp send_welcome_email(_user) do
{:ok, "sent"}
end
end
列表模式匹配
defmodule ListOps do
def sum([]), do: 0
def sum([head | tail]), do: head + sum(tail)
def first([head | _tail]), do: head
def first([]), do: nil
def second([_, second | _]), do: second
def second(_), do: nil
def take_first_three([a, b, c | _rest]) do
[a, b, c]
end
def take_first_three(list), do: list
def split_at_middle(list) do
middle = div(length(list), 2)
{Enum.take(list, middle), Enum.drop(list, middle)}
end
end
映射模式匹配
defmodule UserHandler do
def greet(%{name: name, age: age}) do
"Hello #{name}, you are #{age} years old"
end
def greet(%{name: name}) do
"Hello #{name}"
end
def admin?(%{role: "admin"}), do: true
def admin?(_), do: false
def process_user(%{id: id, name: name} = user) do
# 可以同时使用整个用户和解构的部分
IO.puts("Processing user #{id}: #{name}")
user
end
def update_status(%{status: old_status} = user, new_status) do
%{user | status: new_status}
end
end
defmodule ConfigParser do
def get_database_url(config) do
case config do
%{database: %{host: host, port: port, name: db}} ->
"postgresql://#{host}:#{port}/#{db}"
%{database: %{url: url}} ->
url
_ ->
"postgresql://localhost:5432/default"
end
end
end
结构体模式匹配
defmodule User do
defstruct [:id, :name, :email, role: "user"]
end
defmodule StructMatcher do
def display_user(%User{name: name, email: email}) do
"#{name} <#{email}>"
end
def is_admin?(%User{role: "admin"}), do: true
def is_admin?(%User{}), do: false
def update_email(%User{} = user, new_email) do
%User{user | email: new_email}
end
end
# 使用
user = %User{id: 1, name: "Alice", email: "alice@example.com"}
StructMatcher.display_user(user)
二进制模式匹配
defmodule BinaryParser do
def parse_header(<<
magic::binary-size(4),
version::16,
flags::8,
rest::binary
>>) do
%{
magic: magic,
version: version,
flags: flags,
payload: rest
}
end
def parse_ipv4(<<a, b, c, d>>) do
"#{a}.#{b}.#{c}.#{d}"
end
def parse_utf8(<<codepoint::utf8, rest::binary>>) do
{codepoint, rest}
end
def extract_first_byte(<<first::8, _::binary>>) do
first
end
end
Cond 用于多个条件
defmodule GradeCalculator do
def letter_grade(score) do
cond do
score >= 90 -> "A"
score >= 80 -> "B"
score >= 70 -> "C"
score >= 60 -> "D"
true -> "F"
end
end
def describe_number(n) do
cond do
n < 0 -> "negative"
n == 0 -> "zero"
n > 0 and n < 10 -> "small positive"
n >= 10 and n < 100 -> "medium positive"
true -> "large positive"
end
end
end
高级模式匹配
defmodule AdvancedMatcher do
# 函数参数中的模式匹配与多个子句
def process([]), do: :empty
def process([_]), do: :single
def process([_, _]), do: :pair
def process([h | t]) when length(t) > 1, do: :multiple
# 带有映射和守卫的模式匹配
def format_response(%{status: status, body: body})
when status >= 200 and status < 300 do
{:ok, body}
end
def format_response(%{status: status, body: body})
when status >= 400 do
{:error, body}
end
# 嵌套模式匹配
def extract_user_city(%{
user: %{address: %{city: city}}
}) do
{:ok, city}
end
def extract_user_city(_), do: {:error, :no_city}
# 在for推导式中的模式匹配
def extract_ok_values(results) do
for {:ok, value} <- results, do: value
end
end
何时使用此技能
使用elixir-pattern-matching当您需要:
- 编写表达力强、声明式的控制流
- 使用函数子句处理不同数据形状
- 从复杂数据结构中提取值
- 在函数边界验证数据格式
- 实现带有标记元组的干净错误处理
- 解析二进制数据或协议
- 构建健壮、可维护的Elixir应用程序
- 利用Elixir的函数式编程优势
- 创建清晰、自文档化的代码
最佳实践
- 尽可能使用模式匹配而不是if/else
- 从最具体到最一般排序函数子句
- 使用守卫为模式添加约束
- 当需要现有值时利用pin操作符
- 使用下划线表示您不关心的值
- 优先使用模式匹配而非访问函数
- 对复杂验证管道使用with语句
- 保持模式可读且不过于复杂
- 文档化复杂模式匹配逻辑
- 一致使用标记元组 {:ok, val} 和 {:error, reason}
常见陷阱
- 忘记 = 是模式匹配,不是赋值
- 函数子句排序不正确(具体到一般)
- 当简单模式可行时过度使用守卫
- 未处理所有可能的模式情况
- 因未处理边缘情况而导致MatchError
- 需要时忘记使用pin操作符
- 使模式过于复杂和难以阅读
- 未使用with语句进行多步验证
- 忽略编译器关于未使用变量的警告
- 未利用模式匹配编写更简洁的代码