Erlang Central

Difference between revisions of "Nested Cases"

From ErlangCentral Wiki

m
(3 intermediate revisions by 2 users not shown)
Line 1:Line 1:
 
[[Category:Best Practices]]
 
[[Category:Best Practices]]
 +
[[Category:Articles]]
 
==Author==
 
==Author==
 
[[User:Rvg|Rudolph van Graan]]
 
[[User:Rvg|Rudolph van Graan]]
Line 86:Line 87:
  
 
It is clear that the problem is in do_multiply(...)
 
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  test:function/2
 +
  6: 6> test:function(multiply,[5,2,3]).
 +
  7: ** exception error: no case clause matching {val,[5,2,3]}
 +
  8:      in function  test: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)

Revision as of 04:42, 10 August 2008

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  test:function/2
  6: 6> test:function(multiply,[5,2,3]).
  7: ** exception error: no case clause matching {val,[5,2,3]}
  8:      in function  test: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)