2
头图

This article is a discussion of if statements in the archaeological Erlang language.

grammar

First, how do we think about "if" semantics in Erlang? In the book "Programming in Erlang" written by Armstrong, it is clear that "case and if expressions" are put together in the same section, and the case is introduced first, and the if is introduced later. When introducing if, the given syntax example is as follows:

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

It can be clearly seen from this sentence pattern that the entire if statement is more like a case statement, which executes different clauses through a Guard. The if statement will execute each level. If the result is true, the subexpression will be executed and the end will be executed. If it is false, it will be matched downward until a level is true.

One thing to note here is that the entire statement must ensure that at least one level is true, otherwise the entire statement will throw an exception. Such a statement would be an exception under certain conditions:

 if
  A > 0 ->
    do_this()
end

Unless you intentionally want the error to happen. Of course, an engineer familiar with coding knows that when any hidden intent is confused with possible mistakes, the entire piece of code becomes difficult to read and understand, and even if it is clearly marked with comments, it will be difficult to iterate over later evolutions. The recommended practice, and practice in various projects, is to use the atomic true in the last level, which is guaranteed to match the last subexpression. This is like the last default of case in Java.

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

decision table?

Looking at various Erlang documentation, examples in stackoverflow, and blogs, case will be recommended instead of if. Here is an archived email that clearly illustrates the status of if in the eyes of developers: http://erlang.org/pipermail/erlang-questions/2014-September/080827.html (also cited in the Erlang Coding Specification to illustrate the use case) .

Brief background: Richard A. O'Keefe (a researcher at the University of Otago who has published a number of computational linguistics research papers directly by name) supports the "smart" use of if-statements. The subject of the email he gave was "Praise for the if in Erlang". The description cites a paper that demonstrates the idea that "structured flowcharts are better than pseudocode", and traditional pseudocode is similar:

 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

Logical confusion caused by common nested ifs. In Erlang, by cleverly using Erlang syntax, this piece of logic can be changed into the following pattern:

 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

In essence, it uses semicolons, commas, and whitespace in Erlang to create a visual "decision table" that clearly indicates the conditions corresponding to each branch.

But does this type of writing make your nerves feel some kind of "wonderful trick"? Intuitively, our code should avoid all such tricky but difficult to understand/maintain code, unless this code is essential. Important performance optimization node. Moreover, in today's mature development of compilers, even what you think of as "performance optimization" will often become unrecognizable when compiling, and it must be tested by performance. Often, such optimizations you do can't compare to the compiler at all. optimization done.

back to simplicity

Anthony Ramine first pointed out the troubles caused by this strange indentation to programmers in his reply email.

Secondly, the mixed use of semicolons and commas is difficult to notice in this way, and even typos are difficult to be automatically detected, such as the following example he constructed (here the comma in the third branch is changed to a semicolon):

 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

You can see that this kind of problem happens frequently in Erlang development: https://github.com/rebar/rebar/commit/2c4d7d1d9bdc2a11d3f485f844500bf4c2aa77a2 .

A comment on if in Erlang even goes to this point: https://stackoverflow.com/questions/4327860/erlang-equivalent-to-if-else

"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."

To this end, Anthony prefers to remove the level clause in the if statement, or even stop trying the if clause.

Incidentally, in this example, vegetables that are not green, have no leaves, and are not firm will report an error for not being able to cook.

Coding Standards

In the coding standard we refer to, combined with your development experience, we also put forward the requirement to use less/no if statement.

To transform the if statement in the code, we can use case (which is easier to understand like other languages), and pattern matching is the best choice:

 -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(_, _, _) ->
    [].

References


Hotlink
340 声望7 粉丝

Stay hungry, stay foolish.


引用和评论

0 条评论