檢視原始碼 Inspect 協定 (Elixir v1.16.2)

Inspect 協定將 Elixir 資料結構轉換成代數文件。

這通常在你想要自訂在記錄檔和終端機中如何檢查自己的結構時執行。

此文件說明如何為自己的資料結構實作 Inspect 協定。如需深入了解如何使用檢查,請參閱 Kernel.inspect/2IO.inspect/2

檢查表示

檢查表示通常有三個選項。為了了解它們,我們假設我們有以下 User 結構

defmodule User do
  defstruct [:id, :name, :address]
end

我們的選項是

  1. 使用 Elixir 的結構語法列印結構,例如:%User{address: "Earth", id: 13, name: "Jane"}。這是預設表示,而且如果所有結構欄位都是公開的,這是最佳選擇。

  2. 使用 #User<...> 符號列印,例如:#User<id: 13, name: "Jane", ...>。此符號不會發出有效的 Elixir 程式碼,而且通常在結構有私人欄位時使用(例如,你可能想要隱藏欄位 :address 以刪除個人可識別資訊)。

  3. 使用表達式語法列印結構,例如:User.new(13, "Jane", "Earth")。這假設有一個 User.new/3 函數。此選項大多用作表示自訂資料結構的選項 2 的替代方案,例如 MapSetDate.Range 等。

你可以為自己的結構實作 Inspect 協定,同時遵守上述慣例。選項 1 是預設表示,而且你可以透過衍生 Inspect 協定快速達成選項 2。對於選項 3,你需要自訂實作。

衍生

可以衍生 Inspect 協定,以自訂欄位的順序(預設為字母順序),並隱藏結構中的特定欄位,讓它們不會顯示在記錄、檢查等中。後者對於包含私人資訊的欄位特別有用。

支援的選項為

  • :only - 檢查時僅包含指定的欄位。

  • :except - 檢查時移除指定的欄位。

  • :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 進行。

很多時候,檢查結構可以透過現有實體的函數實作。例如,以下是 MapSetinspect/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

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 的檢查表示會回傳一個表達式,在評估時會建立 MapSet 本身。

錯誤處理

如果在檢查結構時發生錯誤,Elixir 會引發 ArgumentError 錯誤,並自動回退到原始表示法來列印結構。此外,在除錯自己的 Inspect 實作時,您必須小心,因為呼叫 IO.inspect/2dbg/1 可能會觸發無限迴圈(因為要檢查/除錯資料結構,您必須呼叫 inspect 本身)。

以下是幾個提示

  • 對於除錯,請使用 IO.inspect/2 搭配 structs: false 選項,這會停用自訂列印並避免遞迴呼叫 Inspect 實作

  • 若要存取自訂 Inspect 實作上的底層錯誤,您可以直接呼叫協定。例如,我們可以呼叫上述 Inspect.MapSet 實作,如下所示

    Inspect.MapSet.inspect(MapSet.new(), %Inspect.Opts{})

摘要

類型

t()

實作此協定的所有類型。

函式

term 轉換為代數文件。

類型

@type t() :: term()

實作此協定的所有類型。

函式

@spec inspect(t(), Inspect.Opts.t()) :: Inspect.Algebra.t()

term 轉換為代數文件。

此函式不應直接呼叫,除非在實作自訂 inspect_fun 以提供給 Inspect.Opts 時。在其他所有地方,應優先使用 Inspect.Algebra.to_doc/2,因為它會處理結構和例外狀況。