作者
Richard A. O'Keefe <ok(at)cs(dot)otago(dot)ac(dot)nz>
状态
草案
类型
标准跟踪
创建
2008年7月10日
Erlang 版本
OTP_R12B-4

EEP 14: Guard 的澄清和扩展 #

摘要 #

允许使用 Pattern = Guard_Expression 作为简单的 Guard 测试。将明显无意义的 Guard 作为语法错误处理。

规范 #

将 6.24 节 “Guard Sequences” 的开头文本替换如下。

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

一个 <OR guard> 是由分号分隔的 <AND guards> 序列。这里,如同 Erlang 中的其他地方一样,分号表示顺序 OR:一个 <OR guard> 从左到右逐个评估其 <AND guards>,直到其中一个成功或所有都失败。

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

一个 <AND guard> 是由逗号分隔的 <guard tests> 序列。这里,如同 Erlang 中经常发生的情况一样,逗号表示顺序 AND:一个 <AND guard> 从左到右逐个评估其 <guard tests>,直到所有都成功或其中一个失败。

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

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

一个 <guard test> 要么是一个匹配,要么是一个布尔表达式。在一个 Guard 中,当且仅当 <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 ...

是不允许的。

Guard 中的一个 <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 表达式的评估没有副作用并且能够终止。

一个 <guard expr> 由多个子表达式组成

  • 常量
  • 变量
  • 调用 “Guard 表达式中允许的其他 BIF”(见表),使用 <guard expr> 参数
  • 记录字段选择
  • 用括号括起来的 <guard expr>

使用内置的算术和按位运算符组合。

动机 #

这个 EEP 分为两个部分。最初它只是关于允许在 Guard 中进行匹配。后来它变成了两个,因为当前的情况太混乱了,但为了简洁起见,它又变成了一个。

考虑这种情况。一个函数接收一个元组和一个索引。如果该索引处的元素在 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 参考手册中对 Guard 的当前描述非常模糊。令人沮丧的是,这与同样模糊的实现相匹配。该描述将可以用作 Guard BIF 参数(Guard 表达式)的内容与简单的 Guard 混淆了。

考虑以下示例

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

这显然没有任何意义,应该被视为语法错误。根据当前的参考手册,它是合法的;X+1 和 X-1 是合法的“Guard 表达式”。

在 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’ 的所有子句都被消除了,因为它们都是畸形的。更真实的例子只是在运行时悄悄地做错误的事情。

原理 #

允许在 Guard 中进行匹配的语法是显而易见的;没有其他语法是可容忍的。唯一真正的问题是它们是否可以嵌入到 ‘andalso’ 和 ‘orelse’ 中,为了避免回溯问题,我说 “不”。这真的是我能想到的最简单的允许匹配的 Guard 扩展。

EEP 的其余部分旨在在编译时排除明显无意义的 Guard 测试。如何准确地做到这一点是有争议的。但它应该这样做是毋庸置疑的。允许 “27” 和 “X+5” 作为 Guard,我们目前获得了什么好处(除了编译器中不必要的简单性)?

向后兼容性 #

匹配目前在 Guard 中是不允许的,因此添加它们不会破坏任何现有的应用程序代码。显然,任何使用 Erlang 解析树的东西都需要进行扩展。

清理 Guard 中允许的内容可能会影响现有代码。但是,在大多数情况下,编译器已经对此发出了警告,而兼容性问题相当于将警告消息转换为错误消息。

参考实现 #

无。

版权 #

本文档已置于公共领域。