前節のif文と同じく、caseもパターンマッチに似ています。
正直なところ、私は普通の手続き型言語ではほとんどcase文を使いません。
しかし、私はErlangにおいてはif文よりもcase文のほうをよく使います。
それは、Erlangにはパターンマッチがあるからです。
Erlangのcase文は他の言語のそれとは大きく異なります。
とりあえず、caseの書式を見てみましょう。
case 評価する式 of
パターン1 ガード1 ->
処理1;
パターン2 ガード2 ->
処理2;
パターン3 ガード3 ->
処理3
end,
「評価する式」を評価して、その値に対してパターンを上から適用していくのですね。 CやJavaでよく見るサンプルをErlangで書いてみます。
-module(rem5).
-export([get_rem/1]).
get_rem(X) ->
case X rem 5 of
0 -> io:format("X rem 5 is 0~n",[]);
1 -> io:format("X rem 5 is 1~n",[]);
2 -> io:format("X rem 5 is 2~n",[]);
3 -> io:format("X rem 5 is 3~n",[]);
4 -> io:format("X rem 5 is 4~n",[])
end.
これは与えられた数に対する5の剰余を求めるプログラムです。
1> rem5:get_rem(3).
X rem 5 is 3
ok
2> rem5:get_rem(121).
X rem 5 is 1
ok
多少難しいかもしれませんが、今までの内容を盛り込んだサンプルを作ってみました。 まだ再帰関数はやっていないのですが、挑戦してみて下さい。
-module(atom_checker).
-export([check/1]).
check(List) ->
check(List,[]).
check([],CheckedList) ->
lists:reverse(CheckedList);
check([H|T],CheckedList) ->
case is_atom(H) of
true ->
check(T,[{atom,H} | CheckedList]);
false ->
check(T,[{not_atom,H} | CheckedList])
end.
まず、外部に公開されている関数である"check(List)"を見て下さい。 同名でアリティ(引数の数)が異なる関数を呼び出していますね。
Erlangにおいては、「関数名が同じでもアリティが異なると別の関すとして扱われる」ということは覚えておいて下さい。
呼び出されている"check/2"という関数は2つあります。 しかし、よく見ると引数の内容が違います。パターンマッチングで区別されるのでしたね。
check([],CheckedList) は第一引数が空のリストの場合です。 check([H|T],CheckedList) は空でないときとなります。
まず、check([H|T],CheckedList)の動作から見ましょう。
リストのヘッドとテールに分解していますね。
そしてcase式でヘッドがアトムか調べられていて、その評価された値に基づいて分岐されています。
atomなら check(T,[{atom,H} | CheckedList]) が呼び出されています。
これは、自分と同じ関数を呼び出す「再帰関数」ですね。
check/2の第一引数としてテールを渡して、CheckedListの先頭に{atom,H}を追加しています。
後ろではなく先頭に追加するのは効率のためです。
これをぐるぐる回していくと第一引数のリストがどんどん短くなっていき、最終的には空になってしまいますね。 すると、check([],CheckedList)が呼び出されます。 これは lists:reverse(CheckedList)という式を評価していますね。
何をやっているかというと、リストを反転させています。
先ほどリストの先頭にどんどん追加していったのですが、リストの順番が逆になってしまいます。
それを一気に反転させて元に戻してあげた訳です。
この関数は再帰になっていないので終了して、check/1に戻り、実行結果が返されます。
1> List = [hello,3,4,{4,erlang},java].
[hello,3,4,{4,erlang},java]
2> atom_checker:check(List).
[{atom,hello},
{not_atom,3},
{not_atom,4},
{not_atom,{4,erlang}},
{atom,java}]