Home > Erlang, Philosophy > in case in case this happens

in case in case this happens

I truly hate nested case clauses in Erlang they are the root of all that is unholy and should be banned. I have what some consider a fanatic principal when it comes to case clauses and that is: Never have more then one case clause per function. Sure you will argue that it’s not that bad and if you know what you are doing it’s fine and sometimes you can’t avoid it and bla bla bla!

Well guess what, it sucks and I got solid proof to back that!

Why nested case clauses suck

consider this code:

case foo() of
  not_foo ->
    case bar() of
      not_bar ->
        case baz() of
          not_baz ->
            {error, none};
          yes_baz ->
            baz
        end;
     yes_bar ->
        bar
    end;
  yes_foo ->
    foo
end.

And OPS you get a cause_clause error in your live system… and it happens a little bit now and then and you have no idea why or when and your tests sure aren’t finding this really nasty edge case… so What do you do now??. You could try to trace foo(), bar() and baz() but tracing on a live environment isn’t a very good idea… besides you have to leave your trace on for possible hours because the bug might not happen very often! Well you could argue that if you don’t know what the possible return values are you designed it wrong… well that is kindof the point I’m trying to get across! There are a lot of versions on how to “re-write” this.. an example is the classic “construct a tuple” shortcut:

case {foo(), bar(), baz()} of
  {_, _, yes_baz} -> baz;
  {_, yes_bar, _} -> bar;
  {yes_foo, _, _} -> foo
end.

I put the first clause matching the right hand side first on purpose to show that this short cut really doesn’t make sense. In this case you are running 3 commands (which might very well even have dependencies) and you only want to know one of them. In case of true/false functions this is another common type:

case (foo() orelse bar() orelse baz()) of
  true ->
    %% do something...
  false ->
    %% do something else...
end.

problem here is that you don’t know which one succeeded or caused the truth value. In the end I have found that breaking this down in three functions is the best:

f() ->
 case foo() of
   true -> foo;
   false -> b()
 end.

b() ->
 case bar() of
   true -> bar;
   false -> c()
 end.

...

It would still give you a more readable code, if you get an error such as case clause then you know where it is. You wouldn’t want to trace a live system but imagine that you still did, then you would in this case have to trace at least three cases in the nested case clause version while in this version you can trace only 1 (This is still a fairly naive way of looking at it, but it is still better).

NOTE: Seriously, don’t do it though (trace a live system), I turned on a trace in a live system once and by mistake put a trace on a very actively used function, we had to kill the node! but being cleaver as we usually are the node was in such a way that it could safely be killed because we had other nodes that we could fail over on without loosing any data (hahaa!)… but in the end I still was lucky and it was a stupid thing to do :D.

If you have 1 case clause per function and you have small functions then you are much much more likely to know where an error will end up, it becomes clear as day… you know what you called, and with what argument so you should be able to narrow down the reasons for the error. Simply put; you can’t trace case clauses and that means a lot.

I think I shall call this one the “single clause” philosophy (since it probably applies to if clauses as well). The fact is that it has helped me shorten my functions a lot. Probably the number one mistake that people do in functional programming do is to treat functions other then functions; using if statements instead of pattern matching etc. Functions should be small (maybe 5-10 lines, very rarely more) and do only 1 thing (unless it is a glue function which its purpose is to call other functions).

Pattern matching is the power of all righteous.

Advertisements
Categories: Erlang, Philosophy Tags: ,
  1. 2009/10/15 at 13:17

    You could even shorten it more using only pattern matching (as you say). Case clauses are often over used. Example:

    do_something() ->
    f(foo())

    f(not_foo) -> b(bar());
    f(yes_foo) -> foo.

    b(not_bar) -> z(baz());
    b(yes_bar) -> bar.

    z(not_baz) -> {error, none};
    z(yes_baz) -> baz.

  2. mazenharake
    2009/10/15 at 13:40

    Yes you can play around a bit more with it I guess. What you wrote is definitely shorter 😀

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: