• No results found

Catch and Throw

In document erlang book part1 pdf pdf (Page 102-106)

Error Handling

7.1 Catch and Throw

catchand throwprovide a mechanism for monitoring the evaluation of an expres- sion. They can be used for:

Protecting sequential code from errors (catch).

Non-local return from a function (catch combined with throw). 91

The normal effect of failure in the evaluation of an expression (a failed match, etc.) is to cause the process evaluating the expression to terminate abnormally. This default behaviour can be changed usingcatch. This is done by writing:

catch Expression

If failure does not occur in the evaluation of an expression catch Expression

returns the value of the expression. Thus catch atom_to_list(abc) returns

[97,98,99] and catch 22 returns 22.

If failure occurs during the evaluation of an expression, catch Expression re- turns the tuple {’EXIT’, Reason} where Reason is an atom which gives an in- dication of what went wrong (see Section ??). Thus catch an_atom - 2 returns

{’EXIT’,badarith} and catch atom_to_list(123) returns{’EXIT’,badarg}. When a function has been evaluated, control is returned to the caller. throw/1

gives a mechanism for bypassing this. If we evaluate catch Expression as above, and during evaluation of Expressionwe evaluate throw/1, then a direct return is made to the catch. Note that ‘catches’ can be nested; in this case a failure or a throw causes a direct return to the most recent catch. Evaluating throw/1 when not ‘within’ a catch causes a run-time failure.

The following example describes the behaviour ofcatch and throw. We define the function foo/1:

foo(1) -> hello; foo(2) -> throw({myerror, abc}); foo(3) -> tuple_to_list(a); foo(4) -> exit({myExit, 222}).

Suppose a process whose identity is Pid evaluates this function when catch is not involved.

foo(1) – Evaluates to hello.

foo(2) – Causes throw({myerror, abc}) to be evaluated. Since we are not evaluating this within the scope of a catch the process evaluating foo(2)

terminates with an error.

foo(3) – The process evaluating foo(3) evaluates the BIF tuple_to_list(a). This BIF is used to convert a tuple to a list. In this case its argument is not a tuple so the process terminates with an error.

foo(4) – The BIF exit/1 is evaluated. This is not evaluated within a catch

so the process evaluatingfoo(4) terminates. We will see how the argument

Catch and Throw 93

foo(5) – The process evaluating foo(5) terminates with an error since no head of the function foo/1 matches foo(5).

Now we see what happens when we make the same calls to foo/1 within the scope of a catch:

demo(X) ->

case catch foo(X) of {myerror, Args} -> {user_error, Args}; {’EXIT’, What} -> {caught_error, What}; Other -> Other end.

demo(1) – Evaluates to hello as before. Since no failure occurs and we do not evaluate throw, catch returns the result of evaluating foo(1).

demo(2)– Evaluates to{user_error, abc}. throw({myerror, abc})was eval- uated causing the surrounding catch to return {myerror, abc} and case

to return {user_error, abc}.

demo(3)– Evaluates to{caught_error, badarg}. foo(3)fails andcatchevalu- ates to{’EXIT’, badarg}.

demo(4) – Evaluates to {caught_error, {myexit, 222}}.

demo(5) – Evaluates to {caught_error,function_clause}.

Note that, within the scope of a catch, you can easily ‘fake’ a failure by writing

throw({’EXIT’, Message}) - this is a design decision.1

7.1.1 Using catch and throw to guard against bad code

A simple Erlangshell may be written as follows:

-module(s_shell). -export([go/0]). go() ->

eval(io:parse_exprs(’=> ’)), % ’=>’ is the prompt go().

eval({form,Exprs}) ->

case catch eval:exprs(Exprs, []) of % Note the catch {’EXIT’, What} -> io:format("Error: ~w!~n", [What]); {value, What, _} -> io:format("Result: ~w~n", [What]) end; eval(_) -> io:format("Syntax Error!~n", []).

The standard library functionio:parse_exprs/1 reads and parses anErlang

expression returning {form,Exprs} if the expression read is correct.

If correct, the first clauseeval({form,Exprs}) matches and we call the library functioneval:exprs/2to evaluate the expression. We do this within acatchsince we have no way of knowing if the evaluation of the expression will cause a failure or not. For example, evaluating 1 - awould cause an error, but evaluating 1 - a

within a catch catches this error.2 With the catch, the {’EXIT’, What} pattern in the case clause matches when we have a failure and the {value, What, _}

matches for successful evaluation.

7.1.2 Using catch and throw for non-local return of a function

Suppose we want to write a parser to recognise a simple list of integers. This could be written as follows:

parse_list([’[’,’]’|T]) -> {nil, T};

parse_list([’[’, X|T]) when integer(X) -> {Tail, T1} = parse_list_tail(T), {{cons, X, Tail}, T1}.

parse_list_tail([’,’,X|T]) when integer(X) -> {Tail, T1} = parse_list_tail(T), {{cons, X, Tail}, T1}; parse_list_tail([’]’|T]) -> {nil, T}. For example: > parse_list([’[’,12,’,’,20,’]’]). {{cons,12,{cons,20,nil}},[]}

Process Termination 95

If we try to parse an incorrect list the following happens:

> try:parse_list([’[’,12,’,’,a]).

!!! Error in process <0.16.1> in function !!! try:parse_list_tail([’,’,a]) !!! reason function_clause

** exited: function_clause **

Suppose we now want to get out of the recursion and still maintain a knowledge of what went wrong. This could be written as follows:

parse_list1([’[’,’]’|T]) -> {nil, T};

parse_list1([’[’, X|T]) when integer(X) -> {Tail, T1} = parse_list_tail1(T), {{cons, X, Tail}, T1};

parse_list1(X) ->

throw({illegal_token, X}).

parse_list_tail1([’,’,X|T]) when integer(X) -> {Tail, T1} = parse_list_tail1(T), {{cons, X, Tail}, T1}; parse_list_tail1([’]’|T]) -> {nil, T}; parse_list_tail1(X) -> throw({illegal_list_tail, X}).

If we now evaluateparse_list1 within acatch we obtain the following:

> catch parse_list1([’[’,12,’,’,a]). {illegal_list_tail,[’,’,a]}

We have exited directly from within a recursion without following the normal route out of the recursion.

In document erlang book part1 pdf pdf (Page 102-106)

Related documents