作者
Tom Davies <todavies5(at)gmail(dot)com>
狀態
草稿
類型
標準追蹤
建立時間
2023年6月1日

EEP 62:字串插值語法 #

摘要 #

此 EEP 提議新的字串插值語法,允許將表達式嵌入到字串常數中,以使建構複合字串更具可讀性。

例如,新語法

bf"A utf-8 binary string: ~2 + 2~"

會計算為

<<"A utf-8 binary string: 4"/utf8>>

功能概要 #

此提案在兩個軸上添加了四種類型的字串插值(utf-8 二進制或 unicode 碼位列表,以及面向用戶或面向開發人員的格式)。

結果是有四種帶有插值值的通用語法類別

% binary format
<<"A utf-8 binary string: 4"/utf8>> =
  bf"A utf-8 binary string: ~2 + 2~"

% list format
"A unicode codepoint list string: 4" =
  lf"A unicode codepoint list string: ~2 + 2~"

% binary debug
<<"A utf-8 binary string: {4, foo, [x, y, z]}"/utf8>> =
  bd"A utf-8 binary string: ~{2 + 2, foo, [x, y, z]}~"

% list debug
"A unicode codepoint list string: {4, foo, [x, y, z]}" =
  ld"A unicode codepoint list string: ~{2 + 2, foo, [x, y, z]}~"

任意表達式可以嵌套在字串插值替換中,包括變數、函數呼叫、巨集,甚至進一步的字串插值表達式。

設計 #

為什麼同時需要列表字串和二進制字串? #

在 stdlib 中的 string 模組中,字串由 unicode:chardata() 表示,也就是碼位列表、帶有 UTF-8 編碼碼位的二進制(UTF-8 二進制)或兩者的混合。

考慮到這一點,面向列表和面向二進制的字串插值語法接受任何類型的插值值,但是插值的使用者會決定他們想要基於他們使用的插值類型來生成 unicode:char_list()unicode:unicode_binary() (bf"..."bd"..." 來建立二進制,或 lf"..."ld"..." 來建立列表)。

列表字串對於向後相容性和便利性最有用。二進制字串對於記憶體緊湊性和 IO 最有用。

為什麼需要面向用戶和面向開發人員的字串? #

開發人員通常有兩種相似但不同的情況需要格式化字串:當記錄/除錯時,以及當向用戶顯示數據時。

在記錄或除錯時,最重要的功能通常是能夠列印任何類型的術語,並且它應該無損地往返並且被開發人員明確地讀取。這些屬性的例子包括保留執行時類型資訊,例如在格式化字串時保持字串引用,並以完整的範圍和解析度列印浮點數。

當顯示給用戶時,最重要的功能通常是它們始終是人類可讀的且格式整潔。這些屬性的例子包括逐字格式化字串,不帶引號,並且不保留任何 Erlang 式的符號(例如,我們不想列印 Erlang 元組,因為它們對一般應用程式使用者沒有多大意義),因此我們寧願得到一個 badarg 錯誤來迫使開發人員做出明確的格式化決策。

為什麼沒有格式化選項? #

讓我們考慮一下先前介紹的兩個用例

  • 記錄/除錯:通常您想要發射即忘,將您關心的任何值給格式化程式,然後讓它明確地列印該值,這意味著無需調整格式化選項:bd"~Timestamp~: ~Query~ returned ~Result~"
  • 顯示給用戶:通常您想要嚴格控制格式化,並且您可能希望以模組化和可重用的方式進行。在這種情況下,將您的格式化決策分解到一個函數中,然後插值該函數的結果可能是最佳方法:bf"您的帳戶餘額現在是 ~my_app:format_balance(Currency, Balance)~"

值得注意的是,此處的設計和實作並未排除未來引入格式化選項,例如 bf"float: ~.2f(MyFloat)~",就像使用 io_lib:format 等一樣。但是現有的 stdlib 函數可以提供類似的功能,例如 bf"float: ~float_to_binary(MyFloat, [{decimals, 2}, compact])~",並且可以分解為它們自己的可重用函數。

為什麼不使用 Elixir 的語法? #

Elixir 使用 #{...} 在字串中引入插值表達式,並且重複使用該語法可能很方便。不幸的是,這與 Erlang 的 Map 語法衝突。Elixir 的 Map 使用 %{...},因此它沒有該衝突。

實作概要 #

為了解析插值字串,掃描器會追蹤一些關於我們目前是否在插值字串中的額外狀態,此時它會啟用對 ~ 作為插值表達式的分隔符號的識別,並生成新的 Token 來表示插值字串的各種組件。

在編譯和 Shell 評估的早期階段,插值字串被 desugar 成對 io_lib 模組中函數的呼叫,因此不會影響編譯或評估的後續階段。

參考實作 #

PR #7343

向後相容性 #

新的字串插值語法以前不是有效的語法,因此支援新語法的工具應該與現有的原始碼完全向後相容。

新語法將在標準庫中生成對新的二進制建構函數的呼叫,因此使用此新功能編譯的 BEAM 檔案將與較早的版本不相容。

版權 #

本文件置於公共領域或 CC0-1.0-Universal 許可證之下,以較寬鬆者為準。