檢視原始碼 不透明型 (Opaques)
不透明型別名 (Opaque Type Aliases)
在 Erlang 中使用不透明型的主要目的是隱藏資料類型的實作細節,使 API 能夠在演進的同時,將破壞使用者的風險降至最低。執行期不會檢查不透明性。 Dialyzer 提供一些不透明性檢查,但其餘的則取決於慣例。
本文透過 sets:set()
資料類型的範例,說明 Erlang 的不透明性是什麼(以及所涉及的權衡)。此類型曾在 sets
模組中定義如下
-opaque set(Element) :: #set{segs :: segs(Element)}.
OTP 24 在 此 commit 中將定義更改為以下內容。
-opaque set(Element) :: #set{segs :: segs(Element)} | #{Element => ?VALUE}.
而且,如果使用 -opaque
而非 -type
定義類型,此變更會更安全且更具回溯相容性。原因如下:當模組定義 -opaque
時,合約規定只有定義模組應依賴該類型的定義:沒有其他模組應依賴該定義。
這表示在 set
上進行模式比對 (pattern-matched) 作為記錄/元組的程式碼,實際上是違反了合約,並選擇在 set()
的定義變更時可能被破壞。在 OTP 24 之前,此程式碼會印出 ok
。在 OTP 24 中,它可能會發生錯誤
case sets:new() of
Set when is_tuple(Set) ->
io:format("ok")
end.
使用在另一個模組中定義的不透明型時,以下是一些建議
- 請勿使用模式比對、防護 (guards) 或揭露類型的函式(例如
tuple_size/1
)來檢查底層類型。 - 相反地,請使用模組提供的函式來處理該類型。例如,
sets
模組提供sets:new/0
、sets:add_element/2
、sets:is_element/2
等等。 sets:set(a)
是sets:set(a | b)
的子類型,反之則不然。一般來說,您可以仰賴當 T 是 U 的子類型時,the_opaque(T)
是the_opaque(U)
的子類型這一特性。
在定義您自己的不透明型時,以下是一些建議
- 由於預期使用者不會依賴不透明類型的定義,因此您必須提供用於建構、查詢和解構不透明類型實例的函式。例如,集合可以使用
sets:new/0
、sets:from_list/1
、sets:add_element/2
建構,使用sets:is_element/2
查詢,並使用sets:to_list/1
解構。 - 請勿在參數位置使用類型變數定義不透明型。這會破壞正常且預期的行為,例如
my_type(a)
是my_type(a | b)
的子類型 - 將 規格 新增至使用不透明類型的已匯出函式
請注意,對於使用者來說,不透明型可能更難處理,因為預期使用者不會進行模式比對,而必須使用不透明類型作者提供的函式來使用該類型的實例。
此外,Erlang 中的不透明性是表面上的:執行期不會強制執行不透明性檢查。因此,既然集合是以映射 (maps) 的形式實作的,對集合進行 is_map/1
檢查將會通過。不透明性規則僅透過慣例和額外的工具(例如 Dialyzer)強制執行,而且此強制執行並非完全。即使是 sets
的堅決使用者,仍然可以揭露集合的結構,例如透過列印、序列化,或使用集合作為 term/0
,並透過 is_map/1
或 maps:get/2
之類的函式進行檢查。此外,Dialyzer 必須進行一些近似值。