Case to check when list is empty rather then recur

2019-08-04 09:29发布

Im trying to check with a case if a list is empty rather then recursivly catching the pattern when it is, is this the right way to go in Erlang or am i just walking down the wrong path and pattern matching is the best way to catch if a list has been emptied or not?

     calculate([Head|Tail], pi, x, y) ->
         ...calculations of Head being sent to a list...
         case Tail == [] of
            false ->
               calculate(Tail, pi, x, y)
         end.

or should i just pattern match on calculate if the list is empty?

标签: erlang
3条回答
等我变得足够好
2楼-- · 2019-08-04 10:08

add a clause with empty list, and if not possible, one with a single element list:

func([H],P,X,Y) ->
     do_something(H,P,X,Y);
func([H|T],P,X,Y) ->
     do_something(H,P,X,Y),
     func(T,P,X,Y).

Note that this will fail with an empty input list.

Look also if you can use one of the functions lists:map/2 or lists:foldl/3 or list comprehension...

查看更多
狗以群分
3楼-- · 2019-08-04 10:25

Pattern match. Its the Right Thing.

It is also more efficient. It also prevents you from developing a habit of just accepting any sort of variables up front, going partway through your function and discovering that what you've received isn't even a list (oops!). Pattern matching (and using certain types of guards) are also central to the way Dialyzer checks success typings -- which may or not matter to you right now, but certainly will once you start working on the sort of software that has customers.

Most importantly, though, learning to take advantage of pattern matching teaches you to write smaller functions. Writing a huge function with a bajillion parameters that can do everything is certainly possible, and even common in many other languages, but pattern matching will illustrate to you why this is a bad idea as soon as you start writing your match cases. That will help you in ways I can't even begin to describe; it will seep into how you think about programs without you appreciating it at first; it will cut the clutter out of your nested conditions (because they won't exist); it will teach you to stop writing argument error checking code everywhere.

查看更多
迷人小祖宗
4楼-- · 2019-08-04 10:35

Error in your code

General practice is to use function clause with pattern match. It works just as case, and it is considered to much more readable. And it fixes one error you have in your implementation:

First of all your code could be rewritten in this manner.

calculate([Head|Tail], pi, x, y) ->
  %% ... actual calculations ...
  calculate( Tail, pi, x, y);
calculate([], pi, x, y) ->
  %% you need to return something here, but you don't

As you can see, one of clauses do not return anything, which is not allowed in Erlang (fail during compilation). Your implementation does exactly same thing. case just like anything in Erlang must return some value (and since it is lase statement in your function this value will be returned from function). And since case needs to return something, it needs to match on one of it's clauses. It most cases, since Tail == [] will return false it will not be a problem. But at last recursive call, when Tail is empty list, Tail == [] will return true and case will not match to anything. And in Erlang this will cause (throw, or exit to be exact) case_clause error. So your implementation will always fail.

To fix it you need to make sure you always have something matching in you case, like this

case Tail == [] of
  false ->
    calculate(Tail, pi, x, y)
  true ->
    %% return something 
end.

Or it could be written like this

case Tail of
  [] ->
    %% return something sane
  _ -> 
    calculate(Tail, pi, x, y)
end.

where _ will match to anything, and will work somewhat like else is some other languages. And finally it could be written with function clauses, just like I showed before, but with this sane value returned.


EDIT

returning a value

If you look closer at our code wright now we are returning only one value; the one from last recursive call (the one I called "sane"). If you would like to take under account all calculations from all recursive calls you need to accumulate them somehow. And to do this we will use Acc variable

calculate([Head|Tail], pi, x, y, Acc) ->
  %% ... actual calculations with result assigned to Res variable ...
  NewAcc = [Res | Acc]
  calculate(Tail, pi, x, y, NewAcc);
calculate([], pi, x, y, Acc) ->
  Acc.

In each recursive call we add our calculations Res to accumulator Acc, and send this updated list to next level of recursion. And finally, when our input list is empty (we processed all data) we just return whole accumulator. All we need to do, is make sure, that when calculate is being first called, it is called with empty list as Acc. This could be done by new (somewhat old) function

calculate(List, pi, x, y) ->
   calculate(List, pi, x, y, _Acc = []).

Now we can export calculate/4 and keep calculate/5 private.

查看更多
登录 后发表回答