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