Перевожу/цитирую напрямую из новой книги Армстронга. Перевод на скорую руку и не все подряд, так что не обессудьте
Сначала — sequential programming
(стр. 78)
Каждый раз, когда вызывается функция, происходит одна из двух вещей: функция возвращает значение или происходит что-то страшное. Это мы уже видели в предыдущих главах:
shop.erl
cost(oranges) -> 5;
cost(newspaper) -> 8;
cost(apples) -> 2;
cost(pears) -> 9;
cost(milk) -> 7.
Вот мы ее вызываем:
1> shop:cost(apples).
2
2> shop:cost(socks).
=ERROR REPORT==== 30-Oct-2006::20:45:10 ===
Error in process <0.34.0> with exit value:
{function_clause,[{shop,cost,[socks]},
{erl_eval,do_apply,5},
{shell,exprs,6},
{shell,eval_loop,3}]}
Когда мы вызвали cost(socks), функция вылетела (crashed)...
Вызов cost(socks) является бессмыслицей. Функция не может вернуть осмысленное значение, потому что стоимость носков неопределена. Поэтому система генерирует исключение (raises exception).
Мы не пытаемся исправить ошибку, потому что это невозможно. Задача решить, что делать с упавшей функцией лежит на вызывающем.
Исключения гернерируются системой, когда происходят внутренние ошибки, или генерируются вручную с помощью вызовов throw(Exception), exit(Exception). или erlang:error(Exception).
В Эрланге есть два способа перехватить исключения. Один — это обернуть вызов функции в блок try..catch. Второй — обернуть вызов функции в выражение catch
У Эрланга есть структура, похожая на try{}catch{} в Джаве, и выглядит она так:
try FuncOrExpressionSequence of
Pattern1 [when Guard1] -> Expressions1;
Pattern2 [when Guard2] -> Expressions2;
...
catch
ExceptionType: ExPattern1 [when ExGuard1] -> ExExpressions1;
ExceptionType: ExPattern2 [when ExGuard2] -> ExExpressions2;
...
after
AfterExpressions
end
try..catch, как и любое выражение в эрланге являет собой значение. Таким образом следующая краткая запись:
try F
catch
...
end
эквивалентна следующему:
try F of
Val -> Val
catch
...
end
Программируем с try..catch
try_test.erl
generate_exception(1) -> a;
generate_exception(2) -> throw(a);
generate_exception(3) -> exit(a);
generate_exception(4) -> {'EXIT', a};
generate_exception(5) -> erlang:error(a).
demo1() ->
[catcher(I) || I <- [1,2,3,4,5]].
catcher(N) ->
try generate_exception(N) of
Val -> {N, normal, Val}
catch
throw:X -> {N, caught, thrown, X};
exit:X -> {N, caught, exited, X};
error:X -> {N, caught, error, X}
end.
Если мы запустим это дело, то получим:
> try_test:demo1().
[{1,normal,a},
{2,caught,thrown,a},
{3,caught,exited,a},
{4,normal,{'EXIT',a}},
{5,caught,error,a}]
Код, где часто возвращаются ошибки (Code Where Error Returns Are Common)
Если у вашей функции нет "общего случая", то имеет смысл возвращать что-то в стиле {ok, Value} или {error, Reason}, но тогда вызывающий код должен что-то делать с этими значениями. Тогда у вас есть два пути. Писать так:
case f(X) of
{ok, Val} ->
do_some_thing_with(Val);
{error, Why} ->
%% ... делаем что-то с ошибкой ...
end,
...
что обрабатывает все возможные случаи. Или так:
...
{ok, Val} = f(X),
do_some_thing_with(Val);
...
что сгенерирует исключение, если f(X) вернет {error, ...}.
Код, где ошибки возможны, но редки
Обычно вам надо будет написать код, перехватывающий все ошибки. Например, так:
try my_func(X)
catch
throw:{thisError, X} -> ...
throw:{someOtherError, X} -> ...
end
А код, который определяет ошибки, должен содеражть соответствующие вызовы throw:
my_func(X) ->
case ... of
...
... ->
... throw({thisError, ...})
... ->
... throw({someOtherError, ...})
try Expr
catch
_:_ -> ... код для обработки всех исключений
end
Теперь — concurrent programming
Если один процесс зависит от другого, то хочется понаблюдать за здоровьем этого процесса. Для этого используется встроенная функция (BIF) link:
1.
--- ---
|A| ---------------------- |B|
--- ---
А перехватывает прекращение работы (A traps exits)
A связано с В
2.
--- \-/
|A| ---------------------- |/|
--- /-\
В умирает
3.
---
|A| --------- <-- {'EXIT', B, Why}
---
Сигнал о прекращении работы отсылается в А
Мы хотим выполнить действие, когда процесс прекращает работу. Мы можем написать функцию, которая создаст связь с процессом (link to process) Pid. Если Pid умирает по причине Why, тогда вызывается функция Fun(Why):
lib_misc.erl
on_exit(Pid, Fun) ->
spawn(fun() ->
process_flag(trap_exit, true),
link(Pid),
receive
{'EXIT', Pid, Why} ->
Fun(Why)
end
end).
Пример использования:
1> F = fun() ->
receive
X -> list_to_atom(X)
end
end.
#Fun<erl_eval.20.69967518>
2> Pid = spawn(F).
<0.61.0>
3> lib_misc:on_exit(Pid,
fun(Why) ->
io:format(" ~p died with:~p~n",[Pid, Why])
end).
<0.63.0>
4> Pid ! hello.
hello
<0.61.0> died with:{badarg,[{erlang,list_to_atom,[hello]}]}
Когда сигнал о выходе приходит в процесс, все зависит от состояния процесса и значения сигнала согласно таблице:
Идиомы о перехвате сообщений о выходе
Идиома 1. Неважно, если процесс умрет
Процесс просто создает параллельный процесс, используя spawn:
Pid = spawn(fun() -> ... end)
Если второй процесс умрет, первый продолжит работать
Идиома 2. Мы хотим умереть, если созданный нами процесс умрет
spawn_link:
Pid = spawn_link(fun() -> ... end)
Если созданный процесс умрет ненормальной смертью (crashes with a non-normal exit), то наш процесс тоже умрет
Идиома 3. Мы хотим обработать ошибки, если созданный процесс умирает
spawn_link + trap_exit
...
process_flag(trap_exit, true),
Pid = spawn_link(fun() -> ... end),
...
loop(...).
loop(State) ->
receive
{'EXIT', SomePid, Reason} ->
%% do something with the error
loop(State1);
...
end
Дальше идет описание advanced trap_exits, monitors и keep_alive процессы. Но это переводить уже лениво