作者
Richard A. O'Keefe <ok(at)cs(dot)otago(dot)ac(dot)nz>
狀態
草案
類型
標準追蹤
建立於
2008-07-10
Erlang 版本
OTP_R12B-4

EEP 14:守衛條件的澄清與擴展 #

摘要 #

允許使用 Pattern = Guard_Expression 作為簡單的守衛測試。將明顯愚蠢的守衛視為語法錯誤。

規格 #

將 6.24 節「守衛序列」的開頭文字替換如下。

<guard> ::= <OR guard>
<OR guard> ::= <AND guard> {';' <AND guard>}*

一個 <OR guard> 是一系列以分號分隔的 <AND guard>。在 Erlang 中,如同其他地方,分號表示循序 OR:一個 <OR guard> 會從左到右依序評估它的 <AND guard>,直到其中一個成功,或所有都失敗為止。

<AND guard> ::= <guard test> {',' <guard test>}*

一個 <AND guard> 是一系列以逗號分隔的 <guard test>。在 Erlang 中,如同常見的情況,逗號表示循序 AND:一個 <AND guard> 會從左到右依序評估它的 <guard test>,直到所有都成功,或其中一個失敗為止。

<guard test> ::= <guard match>
              | <Boolean expression>

<guard match> ::= <pattern> '=' <guard expr>
               |  <pattern> '=' <guard match>

一個 <guard test> 要嘛是一個匹配,要嘛是一個布林表達式。在守衛條件中,如果 <guard expr> 可以被評估且不拋出例外,並且結果可以與 <pattern> 匹配(可能綁定一些變數),則匹配成功。

如果一個變數在一個 <guard test> 中被綁定,它可以被用於同一個 <AND guard> 後續的 <guard test> 中。如果一個變數在一個 <OR guard> 的所有 <AND guard> 中都被綁定,它可以被用於受守衛的程式碼中,所以

if  X = 1, is_atom(element(X, Tup))
 ;  X = 2, is_atom(element(X, Tup))
 -> ... uses X ...

是可以的。如果一個變數在一個 <OR guard> 的其中一個 <AND guard> 中被綁定,但不是所有,則它不能被用於受守衛的程式碼中,所以

if  X = a
 ;  Y = b
 -> ... uses X ...

是不允許的。

一個守衛條件中的 <Boolean expression> 由一些子表達式組成

constant 'false'
constant 'true'
variable (must be bound to 'false' or 'true')
term comparison with `<guard expr>` operands
calls to type test BIFs with `<guard expr>` operands
`<Boolean expression>`s in parentheses

使用運算符 ‘not’、‘and’、‘or’、‘andalso’ 和 ‘orelse’ 組合而成。因此

X+1 == Y

是一個可以作為 <guard test><Boolean expression>,但是

X+1

則不是。建議永遠不要使用 ‘and’ 和 ‘or’ 運算符,並且盡可能避免使用 ‘andalso’ 和 ‘orelse’,只要使用 ‘,’ 和 ‘;’ 可以達到你的需求即可。

<guard expr> 的集合是有效的 Erlang 表達式的子集。限制有效表達式集合的原因是,必須保證守衛表達式的評估沒有副作用並且會終止。

一個 <guard expr> 由一些子表達式組成

  • 常數
  • 變數
  • 呼叫「其他允許在守衛條件中使用的 BIF」(見表格)並帶有 <guard expr> 參數
  • 記錄欄位選擇
  • 括號中的 <guard expr>

使用內建的算術和位元運算符組合而成。

動機 #

此 EEP 分為兩個部分。最初它只是關於允許在守衛條件中使用匹配。然後它變成了兩個,因為目前的情況太混亂了,但為了簡潔又再次變成一個。

考慮這個例子。一個函數被給定一個元組和一個索引。如果該索引處的元素在 0..127 的範圍內,則應返回該元素。否則應應用其他子句。目前,我們必須寫

f(Tuple, Index)
    when is_integer(element(Tuple, Index)),
     0 =< element(Tuple, Index),
     element(Tuple, Index) =< 127
      -> element(Tuple, Index);
...

或其他更笨拙的方法。為什麼我們不能寫

f(Tuple, Index)
    when X = element(Tuple, Index),
         is_integer(X), 0 =< X, X =< 127
      -> X;
...

在嘗試解釋如何將其添加到語言中時,我發現 Erlang 參考手冊中對守衛條件的當前描述非常模糊。令人沮喪的是,這種情況與同樣模糊的實現相匹配。該描述將可用作守衛 BIF 參數的東西(守衛表達式)與簡單守衛條件混淆了。

考慮這個例子

X = 1,
if X+1 -> true
 ; X-1 -> false
end.

這顯然毫無意義,應該作為錯誤的語法被拒絕。根據目前的參考手冊,它是合法的;X+1 和 X-1 是合法的「守衛表達式」。

在 shell 中,這個例子會崩潰,這確實很有道理。但是 ‘erlc’ 說

{X+1} Warning: the guard for this clause evaluates to 'false'
{X-1} Warning: the guard for this clause evaluates to 'false'

有一個警告是好的,但是警告的文字是錯誤的,這很糟糕。這些東西並不會評估為 ‘false’,它們會評估為數字。然後,儘管給出了警告,您還是會收到一個執行階段錯誤。

exited: {if_clause,[{a,f,0},{shell,exprs,6},{shell,eval_loop,3}]}

當然,在這個例子中發生的事情是 ‘if’ 的所有子句都被刪除了,因為它們都是格式錯誤的。更真實的例子只會在執行階段默默地做錯事。

原理 #

允許在守衛條件中使用匹配的語法是顯而易見的;沒有其他語法是可以接受的。唯一真正的問題是它們是否可以嵌入在 ‘andalso’ 和 ‘orelse’ 內部,為了避免回溯的問題,我說「不」。這確實是我能想到的允許匹配的最簡單的守衛擴展。

EEP 的其餘部分是關於嘗試在編譯時排除明顯愚蠢的守衛測試。確切如何做到這一點是有爭議的。但肯定應該這麼做。允許 “27” 和 “X+5” 作為守衛,我們目前能獲得什麼好處(除了編譯器中不必要的簡潔性)?

向後兼容性 #

目前不允許在守衛條件中使用匹配,因此添加它們不會破壞任何現有的應用程式程式碼。顯然,任何使用 Erlang 解析樹的東西都需要擴展。

清理守衛條件中允許的東西可能會影響現有的程式碼。但是,在大多數情況下,編譯器已經會對此發出警告,並且兼容性問題相當於將警告訊息變成錯誤訊息。

參考實現 #

無。

版權 #

此文件已置於公共領域。