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

EEP 16: is_between/3 #

摘要 #

應該為 guards 新增一個內建函式,

is_between(Term, Lower_Bound, Upper_Bound)

TermLower_BoundUpper_Bound 都是整數,且 Lower_Bound =< Term =< Upper_Bound 時,該函式會成功。

規格 #

新增一個新的 guard BIF。

is_between(Term, LB, UB)

在運算式使用中,如果 LB 或 UB 不是整數,則會拋出 badarith 例外,就像嘗試對非整數引數執行餘數或位元運算一樣。在 guard 使用中,該例外會變成失敗。

這是一個類型測試,如果 Term 是整數且位於 LB 和 UB 之間(包含邊界),則會成功(或返回 true),對於 Term 的其他值則會失敗(或返回 false)。

作為一個運算式,它具有與以下相同的效果:

( X = Term, Y = LB, Z = UB,
  Y bor Z,
  ( is_integer(X), X >= Y, X =< Z )
)

其中 X、Y 和 Z 是未匯出的新變數。

特別是,

is_integer(tom, dick, harry)

應該拋出例外,而不是返回 false,因為 is_integer(Term) 只有在 LB 和 UB 被確定為整數後才會測試。

作為 guard 測試,它具有與以下相同的效果:

( X = Term, Y = LB, Z = UB,
  is_integer(Y), is_integer(Z), is_integer(X),
  X >= Y, X =< Z
)

如果允許這樣做的話。 然而,它允許更有效率的實現方式。

動機 #

目前有些人會這樣測試變數是否為位元組:

-define(is_byte(X), (X >= 0 andalso X =< 255)).

這是目前實際的做法。然而,它無法檢查 X 是否為整數,因此 ?is_byte(1.5) 會成功,它可能會評估 X 兩次,所以 ?is_byte((Pid ! 0)) 會傳送兩則訊息,而不是預期的一則,而目前的 Erlang 編譯器在 guards 中為 ‘andalso’ 和 ‘orelse’ 產生明顯較差的程式碼,而不是為 ‘,’ 和 ‘;’ 產生程式碼。

測試下標是否在範圍內也很有用,

-define(in_range(X, T), (X >= 1 andalso X =< size(T))).

這也有類似的問題。

使用 is_between,我們可以將這些定義替換為

-define(is_byte(X),     is_between(X, 0, 255)).
-define(in_range(X, T), is_between(X, 1, size(T))).

這些定義沒有這些問題

基本原理 #

這個設計的一個替代方案是遵循 Common Lisp 的例子(以及更早的 HP 3000s 上的系統程式語言的例子),並允許

E1 =< E2 =< E3      % (<= E1 E2 E3) in Lisp

(並且可能還有

E1 =< E2 <  E3
E1 <  E2 =< E3
E1 <  E2 <  E3)     % (<  E1 E2 E3) in Lisp

作為 guards 和運算式,每個運算式都只評估一次。我非常喜歡這種語法,也很樂意看到它。這將解決 E2 的重複評估、E3 可能未評估以及 ‘andalso’ 的效率低落問題。然而,它不會解決位元組或索引不僅僅是特定範圍內的 NUMBER,而是 INTEGER 的問題。 如果 Erlang 有多重比較語法,仍然會有使用 is_between/3 的必要性。

向後相容性 #

定義名為 is_between/3 的函式的程式碼將會受到影響。由於 Erlang 編譯器在語意分析之前會解析整個模組,因此很容易

  • 檢查是否有 is_between/3 的定義
  • 如果存在則發出警告
  • 在這種情況下停用新的內建函式。

參考實作 #

目前沒有。然而,我們可以草擬一個。需要兩個新的 BEAM 指令

{test,is_between,Lbl,[Src1,Src2,Src3]}
{bif,is_between,?,[Src1,Src2,Src3],Dst}

測試會執行:

if Src2 is not an integer, goto Lbl.
if Src3 is not an integer, goto Lbl.
if Src1 is not an integer, goto Lbl.
if Src1 < Src2, goto Lbl.
if Src3 < Src1, goto Lbl.

bif 會執行:

if Src2 is not an integer, except!
if Src3 is not an integer, except!
if Src1 is not an integer
or Src1 < Src2
or Src3 < Src1
then move 'false' to Dst
else move 'true'  to Dst.

這裡沒有什麼是全新的,而且只有我不熟悉如何在模擬器中新增指令,才讓我無法完成它。還有我完全不知道如何告訴 HiPE 關於它們的事情!

當 Src2 和 Src3 是整數字面值時,可能會有一些使用這些指令的變體的意義;我當然希望 HiPE 在這裡省略多餘的測試。

編譯器會簡單地識別 is_between/3 並發出適當的 BEAM,就像它識別 is_atom/1 一樣。我對如何擴充模擬器的無知程度超過了我對如何擴充編譯器的無知程度。當然,我們需要

...
is_bif(erlang, is_between, 3) -> true;
...
is_guard_bif(erlang, is_between, 3) -> true;
...
is_pure(erlang, is_between, 3) -> true;
...

(但不要 is_safe 規則) 在 erl_bifs.erl 中。 還是需要呢?我一直無法弄清楚 is_guard_bif/3 是在哪裡被呼叫的。 也需要在 genop.tab 中新增一個條目。 哦,erl_internal.erl.../stdlib 中,而不是 .../compiler 中。 好的,所以需要修補 erl_internal.erl 中的幾個函式來識別 is_between/3;產生 BEAM 需要更改什麼? 惱人的是,如果我熟悉編譯器,添加這個比寫出來更容易。

以下是一些要放入文件中的文字

is_integer(Term, LB, UB) -> bool()

Types:
   Term = term()
   LB = integer()
   UB = integer()

如果 Term 是一個介於 LB 和 UB 之間(包含邊界)的整數 (LB =< Term, Term =< UB),則返回 true;否則返回 false。 在運算式中,如果 LB 或 UB 不是整數,則會引發例外。 UB < LB 不是錯誤。

允許在 guard 測試中使用。

版權 #

本文檔已置於公有領域。