作者
Jesse Gumm <ja(at)gumm(dot)io>
狀態
已接受
類型
標準追蹤
建立於
2024年10月8日

EEP 72:保留字和變數作為記錄名稱,以及定義語法的增強 #

摘要 #

此 EEP 放寬了記錄名稱的一些限制,使其在以保留字(#if 而非 #'if')或首字母大寫的單字(目前會被視為變數的詞,例如 #Hello 而非 #'Hello')命名時,不再需要用引號括起來。

此 EEP 還提議為記錄定義新增類似記錄的新語法(也採用上述語法變更),以便以下記錄定義有效且相同

-record('div', {a :: integer(), b :: integer()}).
-record #div{a :: integer(), b :: integer()}.

後者為提議的新語法。由於屬性中的括號是可選的,而且即使非強制性,原子也可以用引號括起來,因此以下內容也有效且相同

-record 'div', {a :: integer(), b :: integer()}.
-record #'div'{a :: integer(), b :: integer()}.
-record(#'div'{a :: integer(), b :: integer()}).
-record(#div{a :: integer(), b :: integer()}).

使用語法動機 #

記錄名稱是原子。因此,目前的 Erlang 語法要求記錄名稱與語言中原子使用的其餘部分一致。

Erlang 中的所有原子都可以用單引號表示。一些範例

例如

'foo'.
'FOO'.
'foo-bar'.

但方便的是,在所有上下文中,簡單的原子(所有字母數字、底線(_)或 at 符號(@),第一個字元為小寫字母且不是 20 多個保留字之一)都可以在不使用必要的引號包裝的情況下調用。一些範例

foo.
foo_Bar.
'foo-bar'. % still quoted since the term has a non-atomic character in it.

方便的是,這也表示以簡單原子命名的記錄,可以在不引用原子的情況下調用和使用。例如

-record(foo, {a, b}).
-record(bar, {c}).

go() ->
    X = #foo{a = 1, b = 2},
    Y = X#foo{a = something_else},
    Z = #bar{c = Y#foo.a},
    ...

不幸的是,這也表示以任何不符合「簡單原子」模式命名的記錄,必須在定義和使用時用引號包裝。例如

-record('div', {a, b}).
-record('SET', {c}).

go() ->
    X = #'div'{a = 1, b = 2},
    Y = X#'div'{a = something_else},
    Z = #'SET'{c = Y#'div'.a},
    ...

雖然這種方法與語言中的原子使用方式一致,但對於保留字和首字母大寫的原子,如果您需要用保留字(或首字母大寫的詞)命名記錄,則這會使記錄語法感覺不一致。在這種情況下,幾乎可以肯定使用者不會使用名為「if」、「receive」、「fun」等的記錄,即使這種名稱可能有有效的使用案例。從 Nitrogen Web Framework 想到的最常見用例。由於 HTML 有 div 標籤,Nitrogen(使用 Erlang 記錄表示 HTML 標籤)自然應該有 #div 記錄,但是,由於「div」是保留字(整數除法運算符),因此改用 #panel 記錄,以避免程式設計師必須調用 #'div',這會讓人感到不自然和尷尬。

此外,像是 ASN.1 和 Corba 等應用程式都具有嚴重依賴大寫記錄名稱的命名慣例,因此,它們目前也必須用引號括起來。您可以在 Erlang 的 asn1 應用程式中的模組中看到這一點。(先前的連結指向 asn1 中的一些記錄定義,但您可以在 asn1 應用程式中的多個模組中看到其使用方式)。

使用語法規範 #

此 EEP 通過以下方式簡化了上述範例

  1. 允許保留字和變數用於記錄名稱而不使用引號,以及
  2. 簡化定義,使記錄定義和記錄使用之間的語法更加一致。

透過此 EEP 的變更,上述程式碼變為

-record('div', {a, b}).
-record('SET', {c}).

go() ->
    X = #div{a = 1, b = 2},
    Y = X#div{a = something_else},
    Z = #SET{c = Y#div.a},
    ...

定義語法動機 #

雖然使用語法規範中更新的範例使記錄的使用更加簡潔,但仍有一個可以相對容易解決的不一致之處。也就是記錄定義仍然需要引用記錄名稱,如上面的範例所示(為方便起見,在此重複)

-record('div', {a, b}).

go() ->
    X = #div{a = 1, b = 2},
    Y = X#div{a = something_else},
    Z = Y#div.a,
    ...

因此,雖然記錄定義需要被視為 'div',但記錄使用不再需要引用的詞「div」,這肯定會讓 Erlang 初學者想知道為什麼「div」需要在定義中引用,而其他看起來像原子的詞則不需要。

定義語法規範 #

方便的是,有一個相當容易的解決方案,那就是允許將記錄使用語法也用作記錄定義。

此 EEP 也因此新增了新的記錄定義語法,改善了通用記錄使用和記錄定義之間的對稱性。

那麼,上面的範例可以完全如下所示

-record #div{a, b}.

go() ->
    X = #div{a = 1, b = 2},
    Y = X#div{a = something_else},
    Z = Y#div.a,
    ...

實作 #

為了更新使用記錄的語法,我們可以安全地擴充剖析器,以將其現有的記錄處理從 '#' atom '{' ... '}''#'atom '.' atom 變更為 '#' record_name '{' ... '}''#' record_name '.' atom,並定義 record_nameatomvarreserved_word

為了更新記錄定義語法,我們可以簡單地對 attribute 非終端符號新增一些新的修改,以允許 '#' record_name 作為 record 屬性的名稱,而不是像通用屬性那樣的 atom

回溯相容性 #

由於此 EEP 僅新增了新語法,因此絕大多數現有程式碼庫仍然可以運作,但分析使用新語法的程式碼的 AST/程式碼分析工具可能除外。

如果您的程式碼使用新的語法規則,則可能需要更新語法突顯和程式碼完成工具以支援新的語法。

更廣泛的疑慮和討論要點 #

雖然新的定義語法在記錄使用周圍建立了一定程度的對稱性,但要實現完美的對稱性是不可能的,因為記錄始終可以作為它實際所是的原子標記的元組來處理。問題在於要在哪裡劃清界限,讓記錄的真實本質顯現出來,以及我們應該多麼努力地隱藏它。這些是剩餘的疑慮和不一致之處

輔助記錄函式 #

其他與記錄搭配使用的函式,如 is_record/2record_info/1,目前不在本 EEP 的任何語法變更範圍內,因此,如果記錄名稱不是簡單原子,仍然需要引用它們。例如:is_record(X, div) 仍然會是一個語法錯誤。所以仍然沒有真正 100% 的對稱性。請注意,與其使用 is_record(X, 'div') 保護,不如使用 #div{} 匹配更頻繁,因為它更簡潔,並且大多被認為更易讀。

兩種定義語法? #

此 EEP 引入記錄定義的新語法可能會讓某些人想知道,為什麼該語言有兩種相當不同的語法來定義記錄。由於用於取得、設定、匹配等的語法(例如 #rec{a=x,y=b})發生的頻率遠高於定義,因此定義語法反映使用方式僅是自然而然的。

為了更對稱,Erlang 類型系統中用於定義記錄的語法也符合新提議的定義語法。

因此,我認為將現有的使用方式和類型語法與定義系統共用,可能會成為預設/偏好的方式,並且保留原始語法以實現回溯相容性。

參考實作 #

參考實作以 GitHub 上的提取請求形式提供

https://github.com/erlang/otp/pull/7873

版權 #

本文件已置於公有領域。