乐趣区

关于erlang:Erlang中的if赞美与非议

本文是考古 Erlang 语言中对于 if 语句的探讨。

语法

首先,咱们该如何对待 Erlang 中的“if”语义呢?在 Armstrong 编写的《Erlang 程序设计》一书中,很明确地将 ”case 和 if 表达式 ” 一起放在了同一节,并且先介绍了 case,后介绍了 if。在介绍 if 时,给出的语法例子如下:

if
  Guard1 ->
    Expr_seq1;
  Guard2 ->
    Expr_seq2;
  ...
end

能够从这个句式很显著地看出,整个 if 语句更像是一个 case 语句,通过一条条关卡(Guard)宰割执行不同的子句。if 语句会执行每一条关卡,如果后果为 true 则执行子表达式并完结,如果为 false 则顺次向下匹配直到有一个关卡为 true。

这里须要留神的一点是,整个语句必须保障至多有一个关卡为 true,否则整段语句就会抛出异样。这样的语句在特定条件下会是一个异样谬误:

if
  A > 0 ->
    do_this()
end

除非你无意想让谬误产生。当然,一位相熟编码的工程师晓得,任何暗藏的用意与可能的失误混同在一起时,整段代码都会变得难以浏览和了解,即应用正文明确标注,在之后的演进中也会难以迭代。举荐的做法以及各类我的项目中的实际是,在最初一个关卡中应用原子 true,保障匹配最初一条子表达式。这就像 Java 中 case 最初的 default 一样。

if
  Guard1 ->
    Expr_seq1;
  Guard2 ->
    Expr_seq2;
  ...
  true ->
    Expr_default
end

决策表?

查看 Erlang 各类阐明文档,stackoverflow 中的例子,博客,都会优先举荐 case,而非 if。这里有一封归档邮件能够很清晰地阐明 if 在开发者眼中的位置:http://erlang.org/pipermail/e…(也是 Erlang 编码标准中征引的阐明用例)。

简略形容背景:Richard A. O’Keefe(间接搜寻名字就能够找到这位发表了不少计算机语言学钻研论文的 Otago 大学研究员)反对“聪慧”地应用 if 语句。他给出的邮件题目就是“赞美 Erlang 中的 if”。阐明中列举了一篇论文,表明“结构化流程图优于伪代码”的观点,传统的伪代码相似:

IF GREEN
   THEN
      IF CRISPY
         THEN
            STEAM
         ELSE
            CHOP
      ENDIF
   ELSE
      FRY
      IF LEAFY
         THEN
            IF HARD
               THEN
                  GRILL
               ELSE
                  BOIL
            ENDIF
         ELSE
            BAKE
      ENDIF
ENDIF

常见的嵌套 if 引发的逻辑凌乱。在 Erlang 中,通过奇妙地利用 Erlang 语法,能够把这一段逻辑变动为以下模式:

if     Green,     Crispy                    -> steam()
 ;     Green, not Crispy                    -> chop()
 ; not Green,               Leafy,   Hard   -> fry(), grill()
 ; not Green,               Leafy, not Hard -> fry(), boil()
 ; not Green,           not Leafy           -> fry(), bake()
end

实质上是利用 Erlang 中的分号,逗号,空白符,发明出一张视觉上的“决策表”,可能清晰地表明每个分支对应的条件。

不过这种写法是不是让你的神经感触到了某种“奇技淫巧”,直觉上咱们的代码中应该躲避所有这一类写法取巧但难以了解 / 保护的代码,除非这段代码是至关重要的性能优化节点。而且,在编译器倒退成熟的明天,即便是你认为的“性能优化”往往到了编译时会变得面目全非,也肯定要通过性能测试才行,经常你做的这类优化根本无法比上编译器做的优化。

回归俭朴

Anthony Ramine 在回复邮件中首先就指出了这种写法奇怪的缩进给程序员带来的困扰。

其次,分号和逗号的混用在这种形式下难以被留神到,甚至写错了也难以被自动检测进去,例如他构建的以下例子(这里第三个分支的逗号改为了分号):

if     Green,     Crispy                    -> steam()
 ;     Green, not Crispy                    -> chop()
 ; not Green;               Leafy,   Hard   -> fry(), grill()
 ; not Green,               Leafy, not Hard -> fry(), boil()
 ; not Green,           not Leafy           -> fry(), bake()
end

能够看到这类问题在 Erlang 开发中常常产生:https://github.com/rebar/reba…。

对 Erlang 中 if 的评论甚至到了这种境地:https://stackoverflow.com/que…

“I have found that if you are relying on guards or case statements, you are probably ‘doing it wrong’ most of the time in Erlang.”

为此,Anthony 更心愿去掉 if 语句中的关卡子句,甚至不再试用 if 子句。

顺带补充一下,在这个例子中,非绿色,没有叶子,也不坚挺的蔬菜将因为无奈烹饪而报错。

编码标准

在咱们参考的编码标准中,联合大家的开发教训,也提出了少用 / 不必 if 语句的要求。

革新代码中的 if 语句,咱们能够用 case(更容易和其它语言一样了解),而模式匹配是最好的抉择:

-module(no_if).
 
-export([bad/1, better/1, good/1]).
 
bad(Connection) ->
  {Transport, Version} = other_place:get_http_params(),
  if
    Transport =/= cowboy_spdy, Version =:= 'HTTP/1.1' ->
      [{<<"connection">>, utils:atom_to_connection(Connection)}];
    true ->
      []
  end.
 
 
better(Connection) ->
  {Transport, Version} = other_place:get_http_params(),
  case {Transport, Version} of
    {cowboy_spdy, 'HTTP/1.1'} ->
      [{<<"connection">>, utils:atom_to_connection(Connection)}];
    {_, _} ->
      []
  end.
  
 
good(Connection) ->
  {Transport, Version} = other_place:get_http_params(),
  connection_headers(Transport, Version, Connection).
   
connection_headers(cowboy_spdy, 'HTTP/1.1', Connection) ->
    [{<<"connection">>, utils:atom_to_connection(Connection)}];
connection_headers(_, _, _) ->
    [].

参考资料

  • 《Erlang 程序设计》
  • http://erlang.org/pipermail/e…
  • https://github.com/rebar/reba…
  • https://github.com/erlang/otp…
  • https://stackoverflow.com/que…
退出移动版