查看源代码 Inspect 协议 (Elixir v1.16.2)
The Inspect
协议将 Elixir 数据结构转换为代数文档。
通常,当你想要自定义你自己的结构体在日志和终端中的显示方式时,就会用到它。
本文档介绍了如何为你的数据结构实现 Inspect
协议。要了解更多关于 inspect 的使用,请参考 Kernel.inspect/2
和 IO.inspect/2
。
Inspect 表示
通常有三种 inspect 表示的选择。为了理解它们,让我们假设我们有以下的 User
结构体
defmodule User do
defstruct [:id, :name, :address]
end
我们的选择是
使用 Elixir 的结构体语法打印结构体,例如:
%User{address: "Earth", id: 13, name: "Jane"}
。这是默认表示,如果所有结构体字段都是公共的,那么它就是最佳选择。使用
#User<...>
符号打印,例如:#User<id: 13, name: "Jane", ...>
。这种符号不会输出有效的 Elixir 代码,通常用于结构体具有私有字段的情况(例如,你可能想要隐藏:address
字段以屏蔽个人身份信息)。使用表达式语法打印结构体,例如:
User.new(13, "Jane", "Earth")
。这假设存在一个User.new/3
函数。此选项主要用作表示自定义数据结构(例如MapSet
、Date.Range
等)的选项 2 的替代方案。
你可以在遵循上述约定 的情况下为自己的结构体实现 Inspect 协议。选项 1 是默认表示,你可以通过继承 Inspect
协议快速实现选项 2。对于选项 3,你需要自定义实现。
继承
The Inspect
协议可以被继承以自定义字段的顺序(默认情况下按字母顺序排列)并从结构体中隐藏某些字段,这样它们就不会出现在日志、inspect 和类似的地方。后者对于包含私有信息的字段特别有用。
支持的选项有
:only
- 在 inspect 时只包含给定的字段。:except
- 在 inspect 时移除给定的字段。:optional
- (从 v1.14.0 开始) 如果字段与默认值匹配,则不包含该字段。这可以用来简化结构体表示,但会隐藏信息。
无论何时使用 :only
或 :except
来限制字段,结构体都将使用 #User<...>
符号打印,因为结构体不再能被复制粘贴为有效的 Elixir 代码。让我们看一个例子
defmodule User do
@derive {Inspect, only: [:id, :name]}
defstruct [:id, :name, :address]
end
inspect(%User{id: 1, name: "Jane", address: "Earth"})
#=> #User<id: 1, name: "Jane", ...>
如果你只使用 :optional
选项,结构体仍然会以 %User{...}
的形式打印。
自定义实现
你也可以通过定义 inspect/2
函数来定义你自己的协议实现。该函数接收要检查的实体,以及检查选项,这些选项由结构体 Inspect.Opts
表示。代数文档的构建是通过 Inspect.Algebra
完成的。
很多时候,检查一个结构体可以根据现有的实体来实现。例如,以下是 MapSet
的 inspect/2
实现
defimpl Inspect, for: MapSet do
import Inspect.Algebra
def inspect(map_set, opts) do
concat(["MapSet.new(", Inspect.List.inspect(MapSet.to_list(map_set), opts), ")"])
end
end
The concat/1
函数来自 Inspect.Algebra
,它将代数文档连接在一起。在上面的例子中,它将字符串 "MapSet.new("
、由 Inspect.Algebra.to_doc/2
返回的文档以及最后的字符串 ")"
连接在一起。因此,包含数字 1、2 和 3 的 MapSet 将被打印为
iex> MapSet.new([1, 2, 3], fn x -> x * 2 end)
MapSet.new([2, 4, 6])
换句话说,MapSet
的 inspect 表示返回一个表达式,该表达式在被计算后会构建 MapSet
本身。
错误处理
如果在检查你的结构体时出现错误,Elixir 将会抛出 ArgumentError
错误,并将自动回退到原始表示来打印结构体。此外,在调试你自己的 Inspect 实现时,你必须小心,因为调用 IO.inspect/2
或 dbg/1
可能会导致无限循环(因为为了检查/调试数据结构,你必须调用 inspect
本身)。
以下是一些提示
为了调试,请使用
IO.inspect/2
以及structs: false
选项,该选项会禁用自定义打印并避免递归调用 Inspect 实现要访问你自定义的
Inspect
实现中的底层错误,你可以直接调用该协议。例如,我们可以调用上面的Inspect.MapSet
实现,如下所示Inspect.MapSet.inspect(MapSet.new(), %Inspect.Opts{})
总结
函数
将 term
转换为代数文档。
类型
函数
@spec inspect(t(), Inspect.Opts.t()) :: Inspect.Algebra.t()
将 term
转换为代数文档。
除非在实现要传递给 Inspect.Opts
的自定义 inspect_fun
时,否则不应直接调用此函数。在其他所有地方,应优先使用 Inspect.Algebra.to_doc/2
,因为它处理结构体和异常。