Elixir协议 #
一、协议基础 #
1.1 什么是协议 #
协议是Elixir实现多态的方式。协议定义一组函数,不同类型可以实现这些函数。
1.2 定义协议 #
elixir
defprotocol Size do
@doc "Returns the size of a data structure"
def size(data)
end
1.3 实现协议 #
elixir
defimpl Size, for: List do
def size(list), do: length(list)
end
defimpl Size, for: Map do
def size(map), do: map_size(map)
end
defimpl Size, for: Tuple do
def size(tuple), do: tuple_size(tuple)
end
1.4 使用协议 #
elixir
iex(1)> Size.size([1, 2, 3])
3
iex(2)> Size.size(%{a: 1, b: 2})
2
iex(3)> Size.size({1, 2, 3, 4})
4
二、内置协议 #
2.1 String.Chars #
用于 to_string/1:
elixir
defimpl String.Chars, for: Integer do
def to_string(integer), do: Integer.to_string(integer)
end
to_string(42)
2.2 Inspect #
用于 inspect/1:
elixir
defimpl Inspect, for: Map do
def inspect(map, opts) do
Inspect.Algebra.concat(["%{", Inspect.Map.inspect(map, opts), "}"])
end
end
2.3 Enumerable #
用于 Enum 模块函数:
elixir
defimpl Enumerable, for: List do
def count(list), do: {:ok, length(list)}
def member?(list, element), do: {:ok, element in list}
def reduce(list, acc, fun), do: List.foldl(list, acc, fun)
def slice(_list), do: {:error, __MODULE__}
end
2.4 Access #
用于 map[key] 语法:
elixir
defimpl Access, for: Map do
def fetch(map, key), do: Map.fetch(map, key)
def get_and_update(map, key, fun), do: Map.get_and_update(map, key, fun)
def pop(map, key), do: Map.pop(map, key)
end
三、自定义协议 #
3.1 定义协议 #
elixir
defprotocol Json do
@doc "Converts data to JSON string"
def to_json(data)
end
3.2 为内置类型实现 #
elixir
defimpl Json, for: List do
def to_json(list) do
inner = list
|> Enum.map(&Json.to_json/1)
|> Enum.join(",")
"[#{inner}]"
end
end
defimpl Json, for: Map do
def to_json(map) do
inner = map
|> Enum.map(fn {k, v} -> "\"#{k}\":#{Json.to_json(v)}" end)
|> Enum.join(",")
"{#{inner}}"
end
end
defimpl Json, for: Atom do
def to_json(nil), do: "null"
def to_json(true), do: "true"
def to_json(false), do: "false"
def to_json(atom), do: "\"#{atom}\""
end
defimpl Json, for: Integer do
def to_json(integer), do: Integer.to_string(integer)
end
defimpl Json, for: Float do
def to_json(float), do: Float.to_string(float)
end
defimpl Json, for: BitString do
def to_json(string), do: "\"#{string}\""
end
3.3 为自定义类型实现 #
elixir
defmodule User do
defstruct [:name, :email]
end
defimpl Json, for: User do
def to_json(%User{name: name, email: email}) do
"{\"name\":\"#{name}\",\"email\":\"#{email}\"}"
end
end
Json.to_json(%User{name: "Alice", email: "alice@example.com"})
四、协议回退 #
4.1 Any回退 #
elixir
defprotocol Size do
@fallback_to_any true
def size(data)
end
defimpl Size, for: Any do
def size(_), do: 0
end
4.2 自动派生 #
elixir
defmodule MyStruct do
defstruct [:data]
@derive [Size]
defstruct [:data]
end
五、协议继承 #
5.1 结构体派生 #
elixir
defmodule User do
@derive [Jason.Encoder, Inspect]
defstruct [:name, :email, :password]
end
5.2 自定义派生 #
elixir
defprotocol CustomProtocol do
def custom(data)
end
defimpl CustomProtocol, for: Any do
def custom(_), do: :default
end
defmodule MyStruct do
@derive [CustomProtocol]
defstruct [:field]
end
六、协议最佳实践 #
6.1 完整示例 #
elixir
defprotocol Serializable do
@doc "Serializes data to a specific format"
def serialize(data, format \\ :json)
end
defimpl Serializable, for: Map do
def serialize(map, :json), do: Jason.encode!(map)
def serialize(map, :xml), do: map_to_xml(map)
defp map_to_xml(map) do
map
|> Enum.map(fn {k, v} -> "<#{k}>#{v}</#{k}>" end)
|> Enum.join()
end
end
defimpl Serializable, for: List do
def serialize(list, :json), do: Jason.encode!(list)
def serialize(list, :xml) do
inner = list
|> Enum.map(&Serializable.serialize(&1, :xml))
|> Enum.join()
"<items>#{inner}</items>"
end
end
6.2 协议与行为对比 #
| 特性 | 协议 | 行为 |
|---|---|---|
| 多态 | 数据驱动 | 模块驱动 |
| 实现 | 任何类型 | 模块 |
| 分发 | 运行时 | 编译时 |
| 用途 | 数据类型多态 | 模块接口规范 |
七、协议调试 #
7.1 查看协议实现 #
elixir
iex(1)> Size.__protocol__(:impls)
{:consolidated, [List, Map, Tuple]}
7.2 检查协议 #
elixir
iex(1)> function_exported?(Size, :size, 1)
true
八、协议合并 #
8.1 编译时合并 #
协议可以在编译时合并所有实现,提高性能:
elixir
defprotocol Size do
@doc "Returns the size of a data structure"
def size(data)
end
在 mix.exs 中配置:
elixir
def project do
[
app: :my_app,
version: "0.1.0",
elixir: "~> 1.16",
consolidate_protocols: Mix.env() != :test
]
end
九、总结 #
本章学习了:
| 特性 | 示例 |
|---|---|
| 定义协议 | defprotocol Name do ... end |
| 实现协议 | defimpl Name, for: Type do ... end |
| Any回退 | @fallback_to_any true |
| 派生 | @derive [Protocol] |
| 内置协议 | String.Chars, Inspect, Enumerable |
准备好学习模块属性了吗?让我们进入下一章。
最后更新:2026-03-27