How to check if a list is empty in Erlang?

11,056

Pattern matching. If you need to check for an empty list in a guard or if or cond its almost certain that you have a structural problem with the way you are thinking about Erlang.

This will nearly always manifest in confusing code and weird edge cases that make you ask yourself things like "How do I check for an empty list?" without realizing that what you are really asking is "How do I check for an empty list as a procedural condition?" This is the bane of sane functional programming.

Edit: A touch more explanation and an example may be in order

Wherever you would want to inject pattern matching you can either use something like a case or you can break whatever you are doing out into a separate function. Very often what you find is that you've got a semantic ambiguity where things are too tightly coupled on the one hand (you're doing work other than receipt of messages within a receive) and too loosely on the other (you're engaging in a lot of arbitrary procedural checking prior to calling a function, when really matching on parameters is the natural solution).

looper(V, ViewerSet) ->
  receive
    {From, set_value, V} ->
        set_viewer_values(V, ViewerSet),
        looper(V, ViewerSet);
%   OtherStuff ->
%       whatever else looper/2 does...
  end.

set_viewer_values(V, []) ->
    set_default_values(V);
set_viewer_values(V, ViewerSet) ->
    % ... whatever the normal function definition is...

Wherever you are dispatching to from within your receive is what should be doing the actual work, and that is also the place you want to be doing matching. Since that is a function-call away anyway matching here is a good fit and simplifies your code.

If you want to match in looper/2 itself this is certainly possible. I don't know what you want to do when you receive an empty list, so I'll make up something, but you can do whatever you want:

looper(V, []) ->
    looper(V, default_set());
looper(V, ViewerSet) ->
    % As before, or whatever makes sense.

You could even decide that when you have an empty set you need to operate in a totally different way:

full_looper(V, []) ->
    empty_looper(V);
full_looper(V, ViewerSet) ->
  receive
    {new_set, Set} ->
        looper(V, Set);
    {From, set_value, V} ->
        set_viewer_values(V, ViewerSet),
        looper(V, ViewerSet)
  end.

empty_looper(V) ->
  receive
    {new_set, Set} ->
        full_looper(V, Set);
    {From, set_value, V} ->
        set_viewer_values(V, default_set()),
        empty_looper(V)
  end.

My point above is that there are many ways to handle the case of having an empty set without resorting to arbitrary procedural checking, and all of them read easier once you know your way around (until you get used to doing things this way, though, it can feel pretty weird). As a side note, the last example is actually creating a finite state machine -- and there is an OTP module already to make creating FSMs really easy. (They are easy to write by hand in Erlang, too, but even easier with the gen_fsm module.)

Try Case to check when list is empty rather then recursion?

Share:
11,056
sokras
Author by

sokras

Updated on June 04, 2022

Comments

  • sokras
    sokras almost 2 years

    Basically I have a structure that includes a Value and a list of Ids. What I want to do is map over the list of Ids and send a message to them but when I first initialize the list of Ids I put the variable "empty_set".(Maybe I should rename it to empty_list :P).

    The problem is that whenever I call the map function I want to check first if the list is an "empty_set" and if not then use the map function in it. Here is the code:

    {From, set_value, V} ->
      if ViewerSet /= empty_set -> set_viewer_values(V, ViewerSet)
      end,
    looper(V, ViewerSet)
    

    This is the function that is called:

    set_viewer_values(Value, ViewerSet) ->
      if ViewerSet /= empty_set ->
        lists:map(fun(ViewerPid) ->
            ViewerPid ! {self(), set_value, Value} end, ViewerSet)
      end.
    

    This is how I initiate the process:

    process() ->
      C = spawn(fun() -> looper(no_value, empty_set) end),
      {ok, C}.
    

    The problem is that when I run it I get this error:

    =ERROR REPORT==== 2-Nov-2014::15:03:07 ===
    Error in process <0.367.0> with exit value: {function_clause,[{lists,map,
    [#Fun<sheet.2.12938396>,empty_set],[{file,"lists.erl"},{line,1223}]},{lists,map,2,
    [{file,"lists.erl"},{line,1224}]},{sheet,cell_loop,2,[{file,"sheet.erl"},{line,93}]}]}
    

    From what I understand is that despite the if expression that I have to check whether or not the list is empty, it still tries to map over it.

    So what am I doing wrong with the expression?

    Thanks

  • sokras
    sokras over 9 years
    So what you are basically saying is do this? case ViewerSet of empty_list -> looper(V, ViewerSet); [_] -> set_viewer_values(V, ViewerSet)
  • sokras
    sokras over 9 years
    I cant do a pattern match with the looper. Can I?
  • zxq9
    zxq9 over 9 years
    @sokras Pattern matching opportunities exist everywhere in Erlang, you just have to practice seeing them. I added two examples above, one in set_viewer_values/2 and one in looper/2 itself.