本文是考古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_defaultend

决策表?

查看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      ENDIFENDIF

常见的嵌套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...