假设在不使用catch的情况下,一个进程标识为Pid的进程执行了这个函数,则:foo(1) foo(2)
- foo(1) -> hello;foo(2) -> throw({myerror, abc});foo(3) -> tuple_to_list(a);foo(4) -> exit({myExit, 222}).
foo(3)执行throw({myerror,abc})。由于不在catch的作用域内,执行foo(2)的进程将出错退出。
foo(4) foo(5)执行foo(3)的进程执行BIF tuple_to_list(a)。这个BIF用于将元组转换为列表。在这个例子中,参数不是元组,因此该进程将出错退出。
执行foo(5)的进程将出错退出,因为函数foo/1的首部无法匹配foo(5)。
demo(1)
- demo(X) ->
- case catch foo(X) of
- {myerror, Args} ->
- {user_error, Args};
- {'EXIT', What} ->
- {caught_error, What};
- Other ->
- Other
- end.
demo(2) demo(3)像原来一样执行hello。因为没有任何失败发生,而我们也没有执行throw,所以catch直接返回foo(1)的求值结果。
demo(4)求值结果为{caught_error,badarg}。foo(3)执行失败导致catch返回{'EXIT',badarg}。
demo(5)求值结果为{caught_error,{myexit,222}}。
注意,在catch的作用域内,借助{'EXIT',Message},你能够很容易地“伪造”一次失败——这是一个设计决策。
下面来看一个简单的Erlang shell脚本:
正确情况下,应该匹配到第一个子句eval({form,Expr})并调用库函数eval:exprs/2对表达式进行求值。由于无法得知表达式的求值过程是否为失败,我们在此使用catch进行保护。例如,对1-a进行求值将导致错误,但在catch内对1-a求值就可以捕捉这个错误[2]。借助catch,在求值失败时,case子句与模式{'EXIT',what}匹配,在求值成功时则会与{value,What,_}匹配。
使用catch和throw实现函数的非本地返回
假设我们要编写一个用于识别简单整数列表的解析器,可以编写如下的代码:
- parse_list(['[',']' | 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}.
- > parse_list(['[',12,',',20,']']).
- {{cons,12,{cons,20,nil}},[]}
要是我们试图解析一个非法的列表,就会导致如下的错误:
如果我们想在跳出递归调用的同时仍然掌握是哪里发生了错误,可以这样做:
- parse_list1(['[',']' | T]) ->
- {nil, T};
- {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}).
- > catch parse_list1(['[',12,',',a]).
通过这种方式,我们得以从递归中直接退出,而不必沿着通常的递归调用路径逐步折回。