例外とは予期しない動作が起きたときのことを言います。
例えば、足し算をしようと思ったのに変数の中身がatomだった、などということがあげられます。。
ある程度プログラミングをしたことがある人なら分かると思いますが、
複雑なプログラムにおいては例外は必ず生じるものです。
微塵の間違いもない正確なコードを書いたとしても、Erlang言語自体の中にバグもあるでしょうし、
外部的な要因(ネットワークが落ちた)などでも例外は起きます。
障害に強いコードを書くためにも例外処理はきちんと身につける必要があります。
例外処理は少し難しいので、まずは全体像から見て行きましょう。
以下に例外処理の大まかな流れを書きます。
case文に非常に似ていることに注意して下さい。
try 評価される式 of
パターン1 ガード1 -> 処理1;
パターン2 ガード2 -> 処理2;
パターン3 ガード3 -> 処理3
catch
例外のタイプ1: パターン1 ガ−ド1 -> 例外処理1;
例外のタイプ2: パターン2 ガ−ド2 -> 例外処理2;
例外のタイプ3: パターン3 ガ−ド3 -> 例外処理3
after
例外が起きても起きなくても実行される処理
end
まず、"評価される式"が評価されます。
このときエラーが発生したら、catchに飛びます。
なにもエラーが起きなかったらパターンマッチングとガードで任意の処理を行います。
この処理の最中にエラーが起きてもcatchに飛びます。
なにもエラーが起きなかったら通常通りに処理が行われます。
afterキーワードがあれば、通常処理が終わった後に
afterキーワード内の処理を行ってから呼び出し元に帰ります。
afterキーワードがなかったならば、case文と全く同じ動作となります。
さて、ここではエラーが起きたと仮定しましょう。
そうすると、
マッチする例外のタイプ、パターン、ガードの順にマッチしていき、対応する例外処理を行います。
afterキーワードがあればキーワード内の処理を実行し、ないのであればそのまま呼び出し元に帰ります。
これが大まかな流れとなります。
Erlangにおける例外のタイプは3つです。少ないので覚えてしまいましょう。
throw/1
普通の例外として、例外を投げる時に使用する。
この例外を投げられる対象は無視する(自分の上位にまかせる)か、
例外処理をすることが望まれる。
throw(Why)と呼び出す。
exit/1
throwよりも重度の例外であり、プロセスを終了したい場合に投げる。
このプロセスが他のプロセスと協調しているのであれば、
相手のプロセスは何らかの対応が必要となる(後述)。
exit(Why)と呼び出す
error/1
Erlang Run-time(Erlang本体)が出したエラー。エラーとしては一番重度である。
たとえば、1+a などということをやったら発生する。
erlang:error(Why)というような形で任意に呼び出すことも可能であり、
その場合は重度のエラーであることを示している。
サンプルを通して動作を見ていきましょう。
なお、コードはプログラミングErlangのサイトにあったものの引用です。
非常に良い本なので、購入を検討してみては如何でしょうか。
-module(try_test).
-compile(export_all).
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.
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).
簡単に解説します
まず、generate_exception/1関数を見て下さい。
1と4は普通ですね。
2,3,5はエラーを投げています。
この文が実行されるのは
catcher/1関数の中にある
"try generate_exception(N) of"
という部分ですね。
この中で普通の処理(節1と節4)や、エラーが投げられたりしています。
何も投げられなかったら、
"Val -> {N, normal, Val}"
が実行されて、
エラーが投げられたら、
catch
throw:X -> {N, caught, thrown, X};
exit:X -> {N, caught, exited, X};
error:X -> {N, caught, error, X}
end.
の中にあるパターンにマッチしたものが起動されます。
generate_exception(2)は throw(a)なので
"throw:X -> {N, caught, thrown, X};"
がマッチします。他の2つもそれぞれマッチするものがありますね。
実行結果を確認してみましょう
4> try_test:demo1().
[{1,normal,a},
{2,caught,thrown,a},
{3,caught,exited,a},
{4,normal,{'EXIT',a}},
{5,caught,error,a}]
1,4がnormalで、他は例外処理の中の設定になっていますね。
-module(test).
-export([add/2]).
add(X,Y) ->
try check_param(X,Y) of
true -> {ok,X + Y}
catch
throw:Z -> {error,Z};
_:_ -> {error,erlang:get_stacktrace()}
end.
check_param(X,Y) when is_integer(X),is_integer(Y) -> true;
check_param(_X,_Y) -> throw(params_are_illegal).
_:_
は全てのエラーにマッチします。ちなみに、throwやexitもアトムとして表現されています。
erlang:get_stacktrace()は例外の詳細を報告する関数です。
1> test:add(4,5).
{ok,9}
2> test:add(dog,cat).
{error,params_are_illegal}
これはわざと起こしているエラーなので、例外処理を使わなくても処理できます。
たとえば、
add(X,Y) when is_integer(X),is_integer(Y) -> {ok,X+Y};
add(X,Y) -> {error, params_are_illigal}.
とするほうがスマートです。
今回は簡単な関数だったのでいいのですが、try ofの中に複雑な関数を入れたら、 立派に例外処理は働いてくれるでしょう
try catchの最後に便利な手法を書きます。
try 使いたい関数
catch
_:_ -> 例外処理
end
これは関数を実行した時にエラーがおきたら例外処理するというものです。
tryは省略形で使っています。以下のプログラムと同じ意味です。
try function() of
X -> X
catch
_:_ -> do_something
end
例外処理はcatch単体でも可能です。
プログラミングErlangのサイトより引用。
-module(try_test).
-compile(export_all).
demo2() ->
[{I, (catch generate_exception(I))} || I <- [1,2,3,4,5]].
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).
実行結果
3> try_test:demo2().
[{1,a},
{2,a},
{3,{'EXIT',a}},
{4,{'EXIT',a}},
{5,
{'EXIT',{a,[{try_test,generate_exception,1},
{try_test,'-demo2/0-lc$^0/1-0-',1},
{try_test,'-demo2/0-lc$^0/1-0-',1},
{erl_eval,do_apply,5},
{shell,exprs,6},
{shell,eval_exprs,6},
{shell,eval_loop,3}]}}}]
catch 処理
とやって、処理の途中にエラーが起きたら、エラーがタプルとなって帰って来ています。
例えば、
catch generate_exception(3)
は
{'EXIT',a}
が帰って来ていますね。
try catchより柔軟性はないので、細かい処理がしたいときはtry catchを使いましょう。