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

EEP 25: 解嵌套 case 表达式 #

摘要 #

Erlang 的 'case' 表达式应该采用/调整 Algol 68 的一个思想,在 Erlang 中可以严格地推广 'cond'。

规范 #

目前,'case' 表达式的形式为

'case' Expression 'of'
      Pattern ['when' Guard] '->' Expression
 {';' Pattern ['when' Guard] '->' Expression}...
'end'

众所周知,Algol 68 有

if .. then .. {elif .. then ..}... [else ..] fi

表达式。较少为人所知的是,它有一个类似的 case 表达式结构,

case .. in ... {ouse .. in ..}... [out ..] esac

其中 “ouse”(来自 “OUt caSE”)允许你迭代 case 匹配过程,并且只需要一个 'esac'。

本提案采纳了 Algol 68 的思想。修改后的形式是

'case' Expression 'of'
      Pattern ['when' Guard] '->' Expression
 {';' Pattern ['when' Guard] '->' Expression}...
{';' 'or' 'case' Expression 'of'
      Pattern ['when' Guard] '->' Expression
 {';' Pattern ['when' Guard] '->' Expression}...}...
'end'

动机 #

考虑这个例子

suffix(P, Suffix, List)
    when is_function(P, 2), is_list(Suffix) ->
  suffix_loop(P, Suffix, List).

suffix_loop(P, Suffix, List) ->
  case equal(P, Suffix, List)
    of true  -> true
     ; false -> case List
          of [_|Tail] -> suffix_loop(P, Suffix, Tail)
           ; []       -> false
            end
  end.

根据本提案,我们可以这样写

suffix_loop(P, Suffix, List) ->
  case equal(P, Suffix, List)
    of true     -> true
  ; or case List
        of [_|Tail] -> suffix_loop(P, Suffix, Tail)
     ; []       -> false
  end.

其中所有要选择的替代项都具有相同的缩进。

之前关于类似 Lisp 的 'cond' 的提案不再真正需要。代替

cond
    C1 -> B1
  ; C2 -> B2
  ...
  ; Cn -> Bn
end

可以写成

case      C1 of true -> B1
; or case C2 of true -> B2
...
; or case Cn of true -> Bn
end

这里失去的是对结果必须为 'false' 而不是 'true' 的检查,但这项工作现在可以由 Dialyzer 完成。这肯定比 'cond' 笨拙,但它实现了主要目标,即通过一系列布尔值表达式,从逻辑上(因此在相同的缩进级别)的一组选择中进行选择,但它更具有通用性。它允许你将布尔值表达式与守卫(包括未来守卫的任何推广)结合起来,并且允许你基于任何类型的模式匹配进行选择,而不仅仅是布尔值。

这比 'cond' 笨拙,但是当应该使用一些更明确的枚举时过度使用布尔值是一种反模式,这种反模式已经被认识到超过 20 年了。如果存在 'cond',人们会有强烈的压力去编写返回布尔结果的函数,而其他东西可能更有用,只是为了可以使用 'cond'。

举个例子,假设我们希望在电压正常时继续,在电压低且没有紧急情况时关闭设备,或者在电压低且有紧急情况时设置速度慢。

使用 cond

cond voltage_nominal() -> continue_operations()
   ; in_emergency()    -> set_speed_slow()
   ; true              -> shut_device_down()
end

使用 case

case      voltage() of nominal  -> continue_operations()
; or case status() of emergency -> set_speed_slow()
                    ; normal    -> shut_device_down()
end

当以这种方式表达时,我个人更容易意识到“低”不是“正常”的相反;电压不是正常值可能是高值。所以我们真的应该有

case      voltage() of nominal   -> continue_operations()
                     ; high      -> WHAT DO WE DO HERE?
; or case status()  of emergency -> set_speed_slow()
             ; normal    -> shut_device_down()
end

所以一种在巧妙地鼓励 'case' 的多路思维的同时,给你 'cond' 的“扁平”结构的方法是有意义的。你可以说我与其说是支持 'ouse',不如说是反对 'cond' 和过度使用布尔值。

理由 #

我读了太多 “为什么 Erlang 没有 if” 的电子邮件,然后突然想起了 “Algol 68 可以用 ‘case’ 来做到这一点”。

主要问题是如何在 Erlang 中拼写 ‘ouse’。我的第一选择是 ‘or case’,但这行不通。我不喜欢 “; or case”,并且很乐意看到更好的东西。实际上,“; case” 可能可以完成这项工作,我只是觉得它有点容易出错。

向后兼容性 #

所有现有的 Erlang 代码在语义不变的情况下仍然可以接受。实现将完全在解析器中完成,因此即使是检查 AST 的工具也不会受到影响。

参考实现 #

目前还没有。它将完全在解析器中完成。

版权 #

本文档已置于公共领域。