views:

1053

answers:

4

I've got to the stage where I've written quite a bit of Erlang code now, and I can see some style (bad or good) creeping into the way I've been writing it. This particular idiom I'd like some opinion on - is it better (more readable/faster/whatever) to convert case style statements to function pattern matching?

E.g.

Compare (a contrived example)

case {Size > 100000, Type} of
    {true, ets } ->
         %% Do something to convert to dets
         something;
    {false, dets} ->
         %% do something to convert to ets
         somethingelse;
    _ ->
         ignoreit
end;

with

...
maybeChangeStorage(Size, Type)
...

maybeChangeStorage(Size, ets) when Size > 10000 ->
   something;
maybeChangeStorage(Size, dets) when Size < 10000 ->
   somethingelse;
maybeChangeStorage(_,_) ->
   ignoreit.

I prefer the latter in most cases but I'd be interested in other opinion.

A: 

As for me first style is more clear and may be faster. But it need test to say it exactly. In second case if type!=ets then both "Size > 10000" and "Size < 10000" would be evaluated.

JLarky
Yes, I wrote the fragment from memory so the logic may be a little off. One of the things I liked about the first style was the magic construction of a tuple to do the pattern matching - I've used it before with something like {Test1, Test2, Test3, Test4} and then had a whole set of case clauses for the specific combinations I was actually interested in. It beat a whole set of nested if statements!
Alan Moore
Worry about the cost of testing the size of a variable is micro-optimisation. Write the clearest possible code and, when you have an indication based on testing that this particular instance of this construction is too slow, then and only then change it.
Gordon Guthrie
+2  A: 

You can make these examples more similar by doing:

case Type of
   ets  when Size > 10000 -> ...;
   dets when Size < 10000 -> ...;
   _ -> ...
end.

This seems to be clearer to me. The advantage of splitting this to a separate function is that you get to give it a name which acts as documentation and appears in stack traces. If that snippet is part of a larger function I'd separate it out, otherwise it's okay as is.

One thing worth considering is that error case as written the function will accept Type arguments other than ets/dets. Unless this is really what you want it's worth making this clause more restrictive.

cthulahoops
The use of _ as a catch all clause always has a bad smell to me as well - I usually leave it out and let the pattern matching throw it out. But in this particular case I wanted to do nothing if it's not matching. I like the alternative approach of putting the guards in the case clauses...
Alan Moore
+2  A: 

The second is the preferred way especially if you can keep the clauses to a single line:

maybeCngStor(Sz, ets)  when Sz > 10000 -> something;
maybeCngStor(Sz, dets) when Sz < 10000 -> somethingelse;
maybeCngStor(_,_)                      -> ignoreit.

Makes it very easy to read and reason about. Always choose the style that will be easiest to read in the future. Often you find a set of clauses where one is a 10 liner and the rest are one lines only - break out the long one to a function:

maybeCngStor(Sz, ets)  when Sz > 10000 -> something;
maybeCngStor(Sz, dets) when Sz < 10000 -> somethingelse();
maybeCngStor(_,_)                      -> ignoreit.

somethingelse() ->
   (...)
   Return.

Little things like laying out the clauses to align them and using short variable names matter - but don't fall into the trap of changing everything to P, Q, R.

A good trick if you use records a lot is to match out the records to short variables:

#record{foo = F, bar = B, baz = Bz} = Parameter

This gives you short variable names that make sense when you parachute into the function from 10,000 feet looking for a bug next Christmas. F obviously is a Foo, etc, etc...

Gordon Guthrie
I really like the function matching in most cases. Although the cycle for me usually goes something like: write case statement first. refactor into functions later. I tend to write the code as it comes then refactor heavily afterwards. The closer I can get it to one line functions the happier I am.
Jeremy Wall
A: 

(Put as an answer to get the formatting of the code...!)

One thing I did find when I was making some changes is that this approach can alter default short circuiting. E.g.

case A > 10 of 
      true -> 
             case B > 10 of 
                  true -> dummy1; 
                  false -> dummy2 
             end; 
      false -> dummy3 
end

would have to always execute B > 10 if you called it like

doTest(A > 10, B > 10)

when

doTest(true, true) -> dummy1; 
doTest(true, false) -> dummy2; 
doTest(false, _) -> dummy3.

which sometimes isn't what you want!

Alan Moore