此 EEP 提案為所有現有的產生器(列表、位元字串和映射)新增一個新的、嚴格的變體。目前現有的產生器是寬鬆的:它們會忽略右側表達式中與左側模式不匹配的項。另一方面,嚴格的產生器應拋出 badmatch
異常。
引入嚴格產生器的動機是,寬鬆產生器可能會隱藏在推導式輸入資料中出現意外元素。例如,考慮以下程式碼片段:
[{User, Email} || #{user := User, email := Email} <- all_users()]
這個列表推導式會過濾掉沒有電子郵件地址的使用者。如果我們懷疑輸入資料可能不正確,例如 all_users/0
從 JSON 檔案讀取使用者時,這可能會是一個問題。因此,謹慎的程式碼會傾向於崩潰,而不是靜默地過濾掉不正確的輸入,因此必須使用更詳細的 map 函數。
lists:map(fun(#{user := User, email := Email}) -> {User, Email} end,
all_users())
與產生器不同,匿名函式會在沒有電子郵件地址的使用者上崩潰。嚴格產生器也允許在推導式中使用類似的語義。
[{User, Email} || #{user := User, email := Email} <:- all_users()]
如果模式與列表中的元素不匹配,此產生器會崩潰(並產生 badmatch
錯誤)。
針對嚴格產生器提出的運算子為 <:-
(用於列表和映射)和 <:=
(用於位元字串),而不是 <-
和 <=
。選擇此語法的原因是 <:-
和 <:=
在某種程度上類似於測試兩個項是否匹配的 =:=
運算子,同時保持運算子簡短且易於輸入。兩種運算子類型僅相差一個字元 :
,也使運算子容易記住,「:
代表嚴格」。
有很多其他方法可以達到在遇到不匹配的輸入時崩潰的目的。
將推導式轉換為 map
呼叫,就像上一節的範例一樣。
將可能失敗的模式匹配移至推導式的結果表達式中。
[begin #{user:= User, email := Email} = Usr, {User, Email} end || Usr <- all_users()]
將可能失敗的模式匹配移至推導式中的篩選器中(這仍然必須評估為布林值,使得此解決方案看起來非常笨拙)。
[{User, Email} || Usr <- all_users(), (#{user := User, email := Email} = Usr) > 0]
與之前相同,但使用 EEP 12 中提出的繫結器。
[{User, Email} || Usr <- all_users(), #{user := User, email := Email} = Usr]
這些解決方案大多比嚴格產生器語法更冗長,破壞了推導式的簡潔性。但還有更嚴重的問題:
1 不適用於位元字串推導式,因為沒有可用的位元字串 map
函數。
2 將無法在後續的產生器或篩選器中使用模式的子項(本例中的 Usr
)。
在這些解決方案中,程式碼的意圖不明確(絕對不是在 3 中,並且在 4 和 2 中也有爭議)。
這些技術都無法解決位元字串最後不匹配的位元問題。
位元字串產生器的問題在於,與列表和映射不同,位元字串沒有產生器可以迭代的自然「元素」。相反,產生器中的模式決定了將位元字串拆分成多個部分的位置。這不可避免地會導致位元字串以一些與模式不匹配的位元結束的情況,例如以下範例:
[X || <<X:16>> <- <<1,2,3>>]
現有的產生器會跳過這些最後不匹配的位元,並且由於向後相容性的原因,無法更改此行為。只有透過引入新的位元字串產生器類型或其他一些會改變現有產生器行為的新語法,才能保證位元字串產生器會完全消耗其輸入。
目前實作:PR #8625。
提出的新語法沒有向後相容性問題,因為 <:-
和 <:=
運算子在之前的 Erlang 版本中是無效的語法。
本文件置於公有領域或 CC0-1.0-Universal 授權條款下,以較寬鬆者為準。