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