深度模式匹配:嵌套结构解构
模式匹配不仅限于顶层结构,可以任意深度嵌套,直接提取你需要的数据:
# 深度嵌套解构
%{
user: %{
name: name,
address: %{city: city}
}
} = %{
user: %{
name: "Alice",
address: %{city: "北京", zip: "100000"}
}
}
# name = "Alice", city = "北京"
# 列表中解构结构体
[%{id: first_id} | _] = users
# 提取第一个用户的 id
# 函数参数中的模式匹配
defmodule Handler do
def process({:ok, %{status: 200, body: body}}) do
{:ok, body}
end
def process({:ok, %{status: status}}) when status >= 400 do
{:error, "HTTP #{status}"}
end
def process({:error, reason}), do: {:error, reason}
end
case / cond / with
case — 多模式分支
case File.read("config.json") do
{:ok, content} ->
Jason.decode!(content)
{:error, :enoent} ->
IO.puts("配置文件不存在,使用默认配置")
%{}
{:error, reason} ->
raise "读取配置失败: #{inspect(reason)}"
end
cond — 多条件分支(类似 if/else if)
score = 85
grade = cond do
score >= 90 -> "A"
score >= 80 -> "B"
score >= 70 -> "C"
score >= 60 -> "D"
true -> "F" # 默认分支
end
with — 顺序操作,优雅处理错误链
with 是 Elixir 中最优雅的错误处理结构。它将多个可能失败的操作串联,任意一步失败则跳到 else 分支,避免深层嵌套 case。
def register_user(params) do
with {:ok, email} <- validate_email(params["email"]),
{:ok, password} <- hash_password(params["password"]),
{:ok, user} <- create_user(email, password),
{:ok, _email} <- send_welcome_email(user) do
{:ok, user}
else
{:error, :invalid_email} -> {:error, "邮箱格式不正确"}
{:error, :weak_password} -> {:error, "密码强度不足"}
{:error, :email_taken} -> {:error, "邮箱已被注册"}
{:error, reason} -> {:error, "注册失败: #{inspect(reason)}"}
end
end
# 如果不加 else,失败时 with 直接返回失败值(更简洁)
def process(data) do
with {:ok, parsed} <- parse(data),
{:ok, result} <- transform(parsed) do
{:ok, result}
end
end
defstruct:结构体
结构体是带有命名字段和默认值的映射,提供编译时字段检查:
defmodule User do
defstruct [
:id,
:email,
name: "匿名用户",
role: :user,
active: true
]
end
# 创建结构体
alice = %User{id: 1, email: "alice@ex.com", name: "Alice"}
# 访问字段(与 Map 相同)
alice.name # "Alice"
alice.role # :user(默认值)
# 更新(返回新结构体)
%{alice | role: :admin}
# 结构体模式匹配(要求是 User 类型)
def greet(%User{name: name}) do
"Hello, #{name}!"
end
# 访问不存在的字段会报编译错误(安全)
# alice.phone # ** (KeyError)
Protocol:多态接口
Protocol 类似面向对象的接口(interface),允许为不同类型实现相同的行为:
# 定义协议
defprotocol Serializable do
@doc "将数据序列化为字符串"
def serialize(data)
end
# 为 User 结构体实现
defimpl Serializable, for: User do
def serialize(%User{id: id, name: name, email: email}) do
"user:#{id}:#{name}:#{email}"
end
end
# 为 Map 实现
defimpl Serializable, for: Map do
def serialize(map) do
Jason.encode!(map)
end
end
# 使用(多态分派)
Serializable.serialize(%User{id: 1, name: "Alice"})
Serializable.serialize(%{key: "value"})
宏系统:defmacro(元编程)
Elixir 的宏在编译时运行,可以接收 AST(抽象语法树)并返回新的 AST,实现代码生成。这是 Elixir 比 Erlang 强大得多的地方——Phoenix、Ecto 等框架大量使用宏。
defmodule MyMacros do
# 宏接收的参数是 Elixir AST(quoted expression)
defmacro unless(condition, do: block) do
quote do
if !unquote(condition) do
unquote(block)
end
end
end
# 生成日志函数的宏
defmacro logged(name, do: block) do
quote do
def unquote(name)() do
IO.puts("开始执行: #{unquote(Atom.to_string(name))}")
result = unquote(block)
IO.puts("执行完毕")
result
end
end
end
end
defmodule MyModule do
require MyMacros
import MyMacros
unless false do
IO.puts("条件为 false,所以执行")
end
end
宏的使用原则:优先使用函数,只有函数无法实现时才用宏(如需要操作调用者的 AST)。宏使代码难以调试,过度使用会降低可读性。Elixir 社区有一句话:"Write macros as a last resort"。
实战:解析复杂 API 响应
defmodule ApiParser do
defstruct [:id, :name, :repos]
@doc "解析 GitHub API 用户响应"
def parse_github_user(response) do
with {:ok, body} <- extract_body(response),
{:ok, data} <- Jason.decode(body),
{:ok, user} <- build_user(data) do
{:ok, user}
end
end
defp extract_body(%{status: 200, body: body}), do: {:ok, body}
defp extract_body(%{status: 404}), do: {:error, :not_found}
defp extract_body(%{status: status}), do: {:error, "HTTP #{status}"}
defp build_user(%{"id" => id, "login" => name, "public_repos" => repos}) do
{:ok, %ApiParser{id: id, name: name, repos: repos}}
end
defp build_user(_), do: {:error, :invalid_format}
end
本章小结:case 用于单值多模式分支;cond 替代 if/else if 链;with 是顺序操作错误链的最佳实践。defstruct 提供类型安全的数据结构;defprotocol 实现多态;defmacro 是 Elixir 元编程的核心,让框架代码可以创建 DSL。下一章进入 Elixir 最强大的领域——OTP 并发。