作者
Richard A. O'Keefe <ok(at)cs(dot)otago(dot)ac(dot)nz>
状态
最终/R13A/R14A 已在 OTP 版本 R13A 和 R14A 中实现
类型
标准跟踪
创建
2008-07-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 时,我知道这些操作不存在,并且传统的宏存在问题,所以我避免使用它们。作为一个实验,我为我维护的 AWK 版本添加了 max() 和 min() 函数。这很容易,结果是我现在有很多 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 被接受,我很乐意为这些运算符编写文档。

版权 #

本文档已置于公共领域。