Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Erlando コトハジメ

Erlando コトハジメ

更新:2013-09-01
バージョン:0.1.0
作者:@voluntas
URL:http://voluntas.github.com/

R16B系ではすでに使えなくなっています

Erlando

https://github.com/rabbitmq/erlando

サンプル

rebar.config

%% parse_transform を設定する必要あり
{erl_opts, [{parse_transform, do},
            warnings_as_errors,
            warn_export_all,
            warn_unused_import,
            warn_untyped_record]}.

{xref_checks, [fail_on_warning, undefined_function_calls]}.

{clean_files, [".test/*.beam", ".eunit/*", "ebin/*.beam"]}.

{cover_enabled, true}.

{validate_app_modules, true}.

{deps,
  [
   {erlando,
    ".*", {git, "git://github.com/rabbitmq/erlando.git", {branch, "master"}}}
  ]}.

snowflake_error_m.erl

-module(snowflake_error_m).

-export([validate/1]).
-export([convert/1]).

-record(d, {k :: binary(),
            v1 :: integer(),
            v2 :: float()}).

-spec validate(#d{}) -> ok | {error, term()}.
validate(D) ->
    do([error_m ||
        validate_k_size(D),
        validate_has_v1(D),
        validate_has_v2(D)]).

validate_k_size(#d{k = K}) when byte_size(K) > 3 ->
    error_m:return(ok);
validate_k_size(_D) ->
    error_m:fail(invalid_k_size).

validate_has_v1(#d{v1 = undefined}) ->
    error_m:fail(missing_v1);
validate_has_v1(_D) ->
    error_m:return(ok).

validate_has_v2(#d{v2 = undefined}) ->
    error_m:fail(missing_v2);
validate_has_v2(_D) ->
    error_m:return(ok).


convert(D) ->
    do([error_m ||
        V1 <- convert_v1(D),
        V2 <- convert_v2(D),
        return(D#d{v1 = V1, v2 = V2})]).

convert_v1(#d{v1 = V}) ->
    try
        error_m:return(list_to_integer(binary_to_list(V)))
    catch
        _:_ ->
            error_m:fail({invalid_v1, V})
    end.

convert_v2(#d{v2 = V}) ->
    try
        error_m:return(list_to_float(binary_to_list(V)))
    catch
        _:_ ->
            error_m:fail({invalid_v1, V})
    end.



-ifdef(TEST).

-include_lib("eunit/include/eunit.hrl").

validate_test_() ->
    [
        {"ok",
            ?_assertEqual(ok,
                          validate(#d{k = <<"0123">>, v1 = <<"4">>, v2 = <<"5.1">>}))},
        {"invalid_k_size",
            ?_assertEqual({error, invalid_k_size},
                          validate(#d{k = <<"123">>, v1 = <<"4">>, v2 = <<"5.1">>}))},
        {"missing_v1",
            ?_assertEqual({error, missing_v1},
                          validate(#d{k = <<"0123">>}))},
        {"missing_v2",
            ?_assertEqual({error, missing_v2},
                          validate(#d{k = <<"0123">>, v1 = <<"4">>}))}
    ].

convert_test_() ->
    [
        {"ok",
            ?_assertEqual({ok, #d{k = <<"0123">>, v1 = 4, v2 = 5.1}},
                          convert(#d{k = <<"0123">>, v1 = <<"4">>, v2 = <<"5.1">>}))},
        {"invalid_v1",
            ?_assertEqual({error, {invalid_v1, <<"a">>}},
                          convert(#d{k = <<"0123">>, v1 = <<"a">>, v2 = <<"5.1">>}))}
    ].

-endif.

サンプル解説

このサンプルコードでは ok | {error, term()} と {ok, any()} | {error, term()} の二パターンが出てきます。

validate

まず最初の validate ですがこれは「入っている値の確認をしていく」処理です。間違っていたらその場で {error, term()} が戻るようになります。

ポイントは二つです error_m:return(ok) と error_m:fail(term()) です。

何かしら処理をして問題が無ければ error_m:return(ok) を戻します。 もしエラーが起きた場合は error_m:fail(term()) を戻します。

これだけであとはバシバシバリデートを書いていけば良くなります。

%% エラーモナド使用時
-spec validate(#d{}) -> ok | {error, term()}.
validate(D) ->
    do([error_m ||
        validate_k_size(D),
        validate_has_v1(D),
        validate_has_v2(D)]).


%% エラーモナド非使用時
%% 最後も省略せず書いています
validate(D) ->
    case validate_k_size(D) of
        ok ->
            case validate_has_v1(D) of
                ok ->
                    case validate_has_v2(D) of
                        ok ->
                            ok;
                        _ ->
                            ...
                    end
                _ ->
                    ...
            end
        _ ->
            ...
    end

可読性は見てわかるとおりです。error モナドを使う事でとてもシンプルに書くことが出来ます。

convert

複数の値を変換する事も考えられます。その場合は値を取得して次に渡していく必要もあります。

ここで登場するのが error_m:return(term()) です。これを使う事でコードをシンプルに出来ます。

%% エラーモナド使用時
convert(D) ->
    do([error_m ||
        V1 <- convert_v1(D),
        V2 <- convert_v2(D),
        return(D#d{v1 = V1, v2 = V2})]).


%% エラーモナド非使用時
case convert_v1(D) of
    {ok, V1} ->
        case convert_v2(D) of
            {ok, V2} ->
                {ok, D#d{v1 = V1, v2 = V2}};
            _ ->
                ...
        end
    _ ->
        ..
end.

チェックする項目が少ないのでネストは浅いですが、5 個になったら目も当てられません。 エラーモナドを使用することでシンプルに書くことが出来ます。

参考

erlando - ErlangでMaybeモナドとdo記法を使う
http://qiita.com/items/9f43d1b4486f0416ac68
モナドって結局何なのよ?
http://dl.dropbox.com/u/7687891/join_to_Monad/join_to_Monad.html
Erlando
http://www.erlang-factory.com/upload/presentations/435/MatthewSackman.pdf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment