作者
Richard A. O'Keefe <ok(at)cs(dot)otago(dot)ac(dot)nz>
狀態
最終版/R13A/R14A,已於 OTP R13A 和 R14A 版本實作
類型
標準追蹤
建立日期
2008年7月10日
Erlang 版本
OTP_R12B-4

EEP 30:最大值和最小值 #

摘要 #

新增最大值和最小值核心函式。

規格 #

目前 Erlang 語言沒有內建支援最大值和最小值運算。因此我們新增以下函式:

erlang:min(E1, E2),其效果和值與

(T1 = E1, T2 = E2, if T1 > T2 -> T2 ; true -> T1 end)

erlang:max(E1, E2) 具有相同的效果和值,

(T1 = E1, T2 = E2, if T1 > T2 -> T1 ; true -> T2 end)

但我們期望它們使用單一 VM 指令實作,並期望 HiPE 在具備條件移動的機器上使用條件移動。

當且僅當沒有本地定義的 max/2(分別為 min/2)時,max/2(分別為 min/2)上的 erlang: 模組前綴可以省略。

動機 #

最大值和最小值是非常有用的運算。Erlang 沒有標準方式來表達它們,其結果可想而知:在 tool_utilstv_pg_gridfcnstv_pbtv_comm_funcssh_connection_handlerbssh_connection_handlerssh_clihipe_armhipe_schedulehipe_ultra_priohipe_ppc_frame?HIPE_X86_FRAME(據推測,32 位元和 64 位元 PC 各一個)、hipe_sparc_frameerl_recommenterl_syntax_libappmon_info 中都有 max/2 的定義,這個列表還在不斷增加。有數十個副本。min/2 的副本也幾乎一樣多。這還不包括可能使用不同名稱的副本。

這些運算不僅實用,編譯器實作它們也比程式設計師更有效率。如果 X < Y 可以是一個 VM 指令,那麼 minmax 也可以。以下是第一個草稿實作

OpCase(i_minimum): {
    r(0) = CMP_GT(tmp_arg1, tmp_arg2)) ? tmp_arg1 : tmp_arg2;
    Next(1);
}
OpCase(i_maximum): {
    r(0) = CMP_GT(tmp_arg1, tmp_arg2)) ? tmp_arg2 : tmp_arg1;
    Next(1);
}

警告:程式碼未經測試!除此之外,我不知道所有需要更新的地方,或者當新增指令時該如何更新。這些指令預期會像 < 和它的其他朋友一樣,前面加上一個 i_fetch 指令。

這比 Erlang 函式呼叫便宜得多,而且當涉及到兩個浮點數的最大值或最小值時,HiPE 更容易識別,並且可以轉換為比較和條件移動。

最重要的是消除了思考的障礙。當我寫 Fortran 時,我知道 max 和 min 已經存在數十年了,我可以自由地使用這些運算。當我寫 C 時,我知道這些運算不存在,而且傳統巨集存在問題,所以我會避免使用它們。作為一個實驗,我將 max() 和 min() 函式新增到我維護的 AWK 版本中。這很容易,結果是我現在有很多 AWK 程式碼無法被其他任何程式碼執行,因為這些運算非常方便。Erlang 沒有 文件記載的 最大值或最小值函式,除了 lists 模組中的函式之外,而且寫 lists:max([X,Y]) 非常麻煩,足以讓大多數人卻步。

理由 #

函式還是運算子?

我認為有充分的理由使用 理論中的標準 /\\/ 符號。然而,在 EEPs 郵件列表中的討論顯示,社群分為:

  • 熟悉這些運算子的人
  • 堅持認為它們只是布林運算子的人
  • 因為它們不是 C 而根本不了解它們的人。

這些運算能夠作為語言的標準部分使用比它們的名稱更重要,因此為了提高接受度,此 EEP 的第二個草稿改為內建函式。

最終讓我下定決心的是國際化的考慮:日本程式設計師可能使用鍵盤,其中 \ 表示 或螢幕,其中 \ 顯示為日圓符號,因此 /\\/ 對他們來說根本行不通。

我們不能使用 maxmin 作為運算子,因為編譯器不允許將符號同時用作運算子和函式名稱,而且 maxmin 作為函式名稱有很多用途。這正是我們在這裡試圖解決的問題。所以它們必須是函式名稱。

將新的函式新增到 erlang: 模組中並沒有什麼困難。

我不想在這裡寫 erlang: 前綴。對於某些函式,erlang: 前綴是可選的,這也不是什麼新鮮事。

我們想要的是,具有自己 max/2 和/或 min/2 定義的現有模組保持合法,然後只需刪除多餘的定義即可升級。

假設你想找到一組 2D 點的邊界框。(這是從 Wings3D 的程式碼改編而來。)

bounding_box([{X0,Y0}|Pts]) ->
    bounding_box(Pts, X0,X0, Y0,Y0).

bounding_box([{X,Y}|Pts], Xlo,Xhi, Ylo,Yhi) ->
    if X < Xlo -> Xlo1 = X,   Xhi1 = Xhi
     ; X > Xhi -> Xlo1 = Xlo, Xhi1 = X
     ; true    -> Xlo1 = Xlo, Xhi1 = Xhi
    end,
    if Y < Ylo -> Ylo1 = Y,   Yhi1 = Yhi
     ; Y > Yhi -> Ylo1 = Ylo, Yhi1 = Y
     ; true    -> Ylo1 = Ylo, Yhi1 = Yhi
    end,
    bounding_box(Pts, Xlo1,Xhi1, Ylo1,Yhi1);
bounding_box([], Xlo,Xhi, Ylo,Yhi) ->
    {{Xlo,Ylo}, {Xhi,Yhi}}.

使用最大值和最小值運算子,這將變成

bounding_box([{X,Y}|Pts], Xlo,Xhi, Ylo,Yhi) ->
    bounding_box(Pts, min(X,Xlo), max(X,Xhi),
              min(Y,Ylo), max(Y,Yhi));
bounding_box([], Xlo,Xhi, Ylo,Yhi) ->
    {{Xlo,Ylo}, {Xhi,Yhi}}.

回溯相容性 #

沒有問題。如果模組已經有 max/2min/2,則需要使用 erlang: 前綴才能取得新的函式。

參考實作 #

我對 BEAM 或編譯器的了解不夠深入,無法提供實作,但上面提供的指令定義證明對於那些了解的人來說應該不難。如果這個 EEP 被接受,我會很樂意為這些運算子撰寫文件。

著作權 #

本文檔已置於公共領域。