Wednesday, 1 January 2014

Evaluator with error handling

The next version allows for the fact that the core division operation can fail. The natural way to deal with this is to throw an exception and get out of the module up to a higher level. However this means that your function has not properly returned.  The other approach is to plough on to the end, taking care to avoid using anything that depends on a value you couldn't get because of the error.  Everyone has written code like this. Your execution code forks and forks again.  Or rather, everyone has a favourite strategy for avoiding writing code like this.  Subject for a later blog post I think.  Anyway, this is how we can handle the error state in our evaluator.  Intead of just returning a value A we return either the tuple {ok, A}, where A is some integer, representing a successful evaluation, or else the tuple {error, Reason} where Reason is some string explaining what the error is.

Then in the evaluator when we are passed a parameter which evaluates to an error we just pass that error back.  If we are asked to divide by zero we create the error expression.  Otherwise it is calculated as normal.  we just have to add the forks to the code to make this work, like so:

-module(eval_e).
-export([eval/1]).

% evaluator with error handling

eval({con, A}) ->
{ok, A};
eval({dv, T, U}) ->
case eval(T) of
{error, E} -> {error, E};
{ok, A} ->
case eval(U) of
{error, E} -> {error, E};
{ok, B} ->
if
B == 0 -> {error, "divide by zero"};
true -> {ok, A div B}

end
end
end.

{dv, {dv, {con, 1972}, {con, 2}}, {con, 23}}.

error() ->
{dv, {con, 1}, {con, 0}}.

Applying the new evaluator to Answer returns the tuple {ok, 42} indicating a successfully completed computation and showing the result:  applying it to Error returns {error, "divide by zero"}.  These are both legitimate return values for the function.

1> l(eval_e).
{module,eval_e}