Erlang実装パターン2

id:voluntas からskypeで教わった話も一緒に入れています。

EDocスタイルの型宣言

コメントで@spec, @typeというのを使って型の宣言をする。ドキュメントジェネレータ、静的プログラムチェックツールで使用される。これを言語仕様として拡張して入れてしまおうというEEP-8(http://articles.shibu.jp/article/30833304.html)もある。ただし、使っているのはRabbitMQぐらいらしい。EUnitはEDoc。

@typeは型の宣言。@specは関数の入出力の宣言に使うみたい。typeは関数宣言部でも@specの補助として使えるっぽい。@throwsを使うと、例外が投げられる可能性についても書ける。

%% @type moduleName() = atom()
%% @type functionName() = atom()

%% @throws {bad_test, term()}
%%       | {generator_failed, exception()}

%% @spec (testIterator()) -> [integer()]
iter_id(#iter{pos = N, parent = Ns}) ->
    lists:reverse(Ns, [N]).

配列の操作。

nextが空か、そうじゃないかで関数を分けている。典型的なパターン。

# python
def iter_next(I):
  if len(I.next) == 0:
    空のときの処理
  else:
    そうじゃないときの処理

関数直下のif文はこのように分解する。もちろん、caseも使えるはず。

%% Erlang
%% @spec (testIterator()) -> none | {testItem(), testIterator()}

iter_next(I = #iter{next = []}) ->
    nextが空の時の処理;
iter_next(I = #iter{next = [T | Ts]}) ->
    nextが空じゃない時の処理.

if文はcaseに置き換える

Pythonの場合はこう。

if is_string(S):
  true時の処理
else:
  そうではない時の処理

Erlangだとこうかく。

case is_string(S) of
  true -> true時の処理;
  false -> そうではない時の処理;
end

例外処理

try
  正常時の文
catch
  例外の型 -> キャッチしたときの処理
end

throw({型のアトム, 追加情報})
で例外を投げる。

文字列の結合

"test: " ++ S

と結合できる。

try ofという書き方もある

もちろん、 ->をたくさん書けば返り値でパターンマッチで処理の振り分けもできる。ただし、こういうコードは今のところお目にかかったことがない。プログラミングErlangの本の中にもない。 id:voluntas によると、これをやるぐらいなら、tryの返り値に対して、case ofを使った方がいいよ、とのこと。僕もそう思う。

    try M:module_info(exports) of
        Es ->
            Fs = get_module_tests_1(M, Es),
            W = ?DEFAULT_MODULE_WRAPPER_NAME,
            case lists:member({W,1}, Es) of
                false -> Fs;
                true -> {generator, fun () -> M:W(Fs) end}
            end
    catch
        error:undef ->
            throw({module_not_found, M})
    end.

宣言的なコメント

将来はこうしたいけど、今はうまくいかないからこっちを使っている、というコメント。テスティングフレームワークみたいに、自分で自分のテストなど、ドッグフード系のシステム開発にはこういうのは不可欠かも。Erlangに限った話ではないけど。

%%{application, eunit}. % this currently causes a loop
%% We use the below until loop detection is implemented

タプルをそのまま画面に出力できない

io:format(atom).

はOKだけど

io:format({atom, "sample test"}).

はエラーで落ちる。ちなみに、formatは引数を一つだけとれる。一つしか取れない。

whenはパターンマッチ後に使える

whenを指定すると、パターンマッチの情報を詳細に指定できる。whenにはオリジナルの関数はつっこめないけど、マクロはOK。