作者
Richard A. O'Keefe <ok(at)cs(dot)otago(dot)ac(dot)nz>
状态
草案
类型
标准跟踪
创建时间
2008年7月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(包括 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,因为只有在发现 LB 和 UB 是整数之后才会测试 is_integer(Term)

作为 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 3000 上的系统编程语言的更早示例)并允许

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’ 的低效率问题。但是,它不会解决字节或索引不仅仅是某个范围内的数字而是整数的问题。如果 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 和 UB)之间的整数(LB =< Term,Term =< UB),则返回 true;否则返回 false。在表达式中,如果 LB 或 UB 不是整数,则会引发异常。UB < LB 不是错误。

允许在 guard 测试中使用。

版权 #

本文档已置于公共领域。