読者です 読者をやめる 読者になる 読者になる

Erlang 基礎ポイント3 - case式, if式 例外

case式

Erlang 基礎ポイント2 - DukeLabでパターン集合について説明しました。
パターン集合の結果から処理を複数の関数に分けることができましたが、
関数の数が無駄に多くなる恐れがありますね。

もちろん、プログラムの処理は可能な限り小さくした方が
モジュール化側面でいいでしょうが、やりすぎると、
かえって複雑なプログラムになりかねないですね。

case式を使うと、一つの関数内で処理部を分けておくことができます。
Javaのswitch caseのようなものですが、判断条件に固定値だけでなく、パターンが使えるので、
もっと強力です。

まず、例をみてみましょう。
前のguard_module.erlがErlang 基礎ポイント2 - DukeLabのパターン集合節で紹介したもので
後のcase_test.erlがcase式を使うように修正したものです。

% guard_module.erl
-module(guard_module).
-export([guard_test/1]).

guard_test(X) when X < 5, X > 1 -> X * X;
guard_test(X) when X >= 5; X < 10 -> X - 2;
guard_test(X) -> X - 2.

% case_test.erl
-module(case_test).
-export([guard_test/1]).

guard_test(X) ->
  case X of
    X when X < 5, X > 1 -> X * X;
    X when X >= 5; X < 10 -> X - 2;
    X -> X - 2 
  end.

caseの使い方は次のようです。
他のプログラミングの経験があるなら、問題ないでしょう。

case 評価対象 of
  パターン1 [when ガード...] -> 結果;
  ...

  パターンn [when ガード...] -> 結果
end   

最後のパターンにはセミコロンをつけないことに注意して下さい。
評価対象に判断対象(関数引数・変数など)を指定し、
パターンnに判断対象と照合するパターンを書きます。
必要ならガードに追加条件を指定できますが、ガードは省略可能です。
評価対象がパターンに一致すれば、結果が返されます。
一致するパターンがないと、例外が発生するので、注意しましょう。

caseの方が関数の長さが増えましたが、関数の数は減りましたね。
どちらも優先的に使うのではなく、関数の数とcaseの数をバランスよく
配置すればいいと思います。

if式

他のプログラミング言語のif文と同じですが、違いは式なので、戻り値があるということです。
上記のcase式のサンプルをif式に修正したものを載せます。

% if_test.erl
-module(if_test).
-export([if_test/1]).

if_test(X) ->
  if 
    X < 5, X > 1 ->
      X * X;
    X >= 5, X < 10 ->
      X - 2;
    true ->
      X - 3;
  end.

% if_testをREPL環境で試す。
19> c(if_test).        
{ok,if_test}
20> if_test:if_test(7).
5
21> c(if_test).        
{ok,if_test}
22> if_test:if_test(3).
9
23> if_test:if_test(6).
4
24> if_test:if_test(-1).
-4

if式の文法は他のプログラミング言語のif文と似ていますが、ちょっと違いますね。
まず、複数個の条件を書く時、ifと複数書く必要がありません。
最初にifと書いて、case式のように条件と結果を次々と書いていけばいいです。
それから、最後の条件にtrueと書きましたね。これはelseと考えればいいです。
Erlangのifにはelseがありません。なので、全ての条件に一致しない時の条件を
直接書く必要がありますが、trueと書いておけばOKです。

case 評価対象 ->
  パターン1 [when ガード...] -> 結果;
  ...

  パターンn [when ガード...] -> 結果
end   

trueを書かないで、全ての条件に一致しない場合、例外が発生しますので、注意しましょう。

例外

Erlangで例外処理は、ちょっと形式は違いますが、Javaと同じくtry-catch式を使います。
以下の例を見ながら、try-catchを身につけましょう。

% trycatch.erl
-module(trycatch).
-export([go/1]).

go(X) ->
    try test(X) of
        Val -> {value, Val}
    catch
        throw:exception1 -> io:format("Catch Exception_Test1~n");
        throw:exception2 -> io:format("Catch Exception_Test2~n");
        exit:_           -> io:format("Catch Exit~n");
        error:_          -> io:format("Catch Error~n")
    after
        io:format("After~n")
    end
.

test(X) ->
    case X of
        1 -> throw(exception1);
        2 -> throw(exception2);
        3 -> exit(unknown);
        4 -> error(unknown)
    end
.

% trycatchをREPL環境で試す。
1> c(trycatch).
{ok,trycatch}
2> trycatch:go(1).
Catch Exception_Test1
After
ok
3> trycatch:go(2).
Catch Exception_Test2
After
ok
4> trycatch:go(3).
Catch Exit
After
ok
5> trycatch:go(4).
Catch Error
After
ok

関数goに1から4までの数字を入れると、例外(throw)2種類、プロセス終了(exit)1種類、エラー(error)1種類を演じてくれます。
throwは呼出元が例外を処理することを期待する時、呼出元に対してエラーをなげる関数です。JavaのChecked Exceptionと似ていますね。
但し、throwでエラーを発生させても、呼出元がtry-catchでエラーを捉えなくても実行はできます
Javaのようにコンパイルエラーは発生しません。
プログラムで捉えられなかったエラーはErlangが捉えます。その場合、スタックトレースのようなものが表示されるでしょう。

exitはプロセスを終了させたい時、呼出元にエラーをなげる関数です。try-catchでこのエラーを捉えないと、プロセスが終了してしまいます。

errorは呼出元が例外を処理できないような致命的なエラーが発生したことを知らせるため関数です。
Javaでいうと、RuntimeExceptionのようなものでしょうか。

よく見るとErlangのtry-catchはcaseと似ていることが分かります。
tryとcatchの間のコードがまさにcaseのような形式、その後、catchとafterが追加されていますね。
catchはJavaのcatchと同じで、afterはJavaのfinallyのようなものです。

以下はtry-catch式の使い方です。

try 評価対象 of
  パターン1 [when ガード...] -> 結果;
  ...

  パターンn [when ガード...] -> 結果
catch
    例外種類1(throw, exit, error): 例外パターン1 [when ガード1] -> 結果1;
    ...
    例外種類n(throw, exit, error): 例外パターンn [when ガードn] -> 結果n
after
    catch結果に関係なく、返す結果
end

Erlangのtry-catchはJavaと違って、式なので戻り値があります

今日はここまで。
次はErlangでの並行処理について書きます。