Erlang Central

Nested Cases

Revision as of 04:39, 10 August 2008 by Ravindranathakila (Talk | contribs)

Category:Best Practices]]

Author

Rudolph van Graan

Article

This article describes one of the Erlang best practices. It is particularly difficult to trace errors when a programmer used many nested case statements in a single function.

Consider the following code fragment that implements a basic operation table:

  1: function(Operation,Values) ->
  2:   case Operation of
  3:     null -> 
  4:       null;
  5:     multiply -> 
  6:       case Values of 
  7:         [A,B] when integer(A), 
  8: 	               integer(B) -> 
  9: 	      {ok,A*B}
10:       end
11:     end.

As you can see, it contains a nested case (line 6).If we execute the multiplication operation, the code performs as expected:

(rvg@davinci)5> test:function(multiply,[5,2]).
{ok,10} 

Now, let us try an invalid operation:

(rvg@davinci)6> test:function(divide,[5,2]).

=ERROR REPORT==== 25-Dec-2004::20:30:28 ===
Error in process <0.43.0> on node 'rvg@davinci' with exit value: 
{{case_clause,divide},[{test,function,2},{shell,exprs,6},{shell,eval_loop,3}]}

** exited: {{case_clause,divide},
            [{test,function,2},{shell,exprs,6},{shell,eval_loop,3}]} **

As expected, the shell crashed with a case_clause. Now, let us try the same with invalid multiplication parameters:

(rvg@davinci)7> test:function(multiply,[5,2,3]).

=ERROR REPORT==== 25-Dec-2004::20:33:05 ===
Error in process <0.47.0> on node 'rvg@davinci' with exit value:
 {{case_clause,[5,2,3]},[{test,function,2},{shell,exprs,6},{shell,eval_loop,3}]}

** exited: {{case_clause,[5,2,3]},
            [{test,function,2},{shell,exprs,6},{shell,eval_loop,3}]} **

As you can see, the reason is still {case_clause,...}. In complex code, you would not easily figure out which case caused the problem. Let us fix the code:

 1: function(Operation,Values) ->
 2:   case Operation of
 3:     null -> 
 4:       null;
 5:     multiply ->
 6:       do_multiply(Values)
 7:   end.
 8: 
 9: do_multiply([Value1,Value2]) when integer(Value1),
10:                                   integer(Value2)->
11:   {ok,Value1*Value2}.

If we pass an invalid operation:

(rvg@davinci)11> test:function(divide,[5,2]).

=ERROR REPORT==== 25-Dec-2004::20:37:54 ===
Error in process <0.60.0> on node 'rvg@davinci' with exit value: 
{{case_clause,divide},[{test,function,2},{shell,exprs,6},{shell,eval_loop,3}]} 

** exited: {{case_clause,divide},
            [{test,function,2},{shell,exprs,6},{shell,eval_loop,3}]} **

You can see that the case clause in function(...) cannot handle the divide atom. If we pass invalid parameters:

(rvg@davinci)12> test:function(multiply,[5,2,3]). 

=ERROR REPORT==== 25-Dec-2004::20:38:54 ===
Error in process <0.62.0> on node 'rvg@davinci' with exit value:
 {function_clause,[{test,do_multiply,5,2,3},{erl_eval,do_apply,5},{shell,exprs,6},{shell,eval_loop,3}]}

** exited: {function_clause,[{test,do_multiply,5,2,3},
                             {erl_eval,do_apply,5},
                             {shell,exprs,6},
                             {shell,eval_loop,3}]} **

It is clear that the problem is in do_multiply(...)


Better Solution:

  1: -module(test).
  2: -export([function/2]).
  3: function(Oper,Val)->
  4:	case {oper,Oper} of
  5:		{oper,null}->
  6:			null;
  7:		{oper,multiply}->
  8:			case {val,Val} of
  9:				{val,[A,B]} when integer(A), integer(B)
 10:					-> {ok,A*B}
 11:			end
 12:	end.


  1: 4> test:function(multiply,[5,2]).
  2: {ok,10}
  3: 5> test:function(divide,[5,2]).
  4: ** exception error: no case clause matching {oper,divide}
  5:      in function  t:function/2
  6: 6> test:function(multiply,[5,2,3]).
  7: ** exception error: no case clause matching {val,[5,2,3]}
  8:      in function  t:function/2

We've got much more clarity here as we KNOW where to find the atoms 'val' and 'oper'. We could use the atoms 'case1' and 'case2' if we would like to see which case caused the error explicitly. (Handling null cases is defensive)