更新: | 2013-09-01 |
---|---|
バージョン: | 0.1.0 |
作者: | @voluntas |
URL: | http://voluntas.github.com/ |
R16B系ではすでに使えなくなっています
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 ですがこれは「入っている値の確認をしていく」処理です。間違っていたらその場で {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 モナドを使う事でとてもシンプルに書くことが出来ます。
複数の値を変換する事も考えられます。その場合は値を取得して次に渡していく必要もあります。
ここで登場するのが 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