C言語でこういうコードがあったとする。
int f(Z *z) { int result; W w; X x; Y y; Z z; result = hoge(&w); if (result != E_OK) return result; result = fuga(&w, &x); if (result != E_OK) return result; result = piyo(&w, &x, &y); if (result != E_OK) { /* piyoのエラーはログを出す */ log("error in piyo !!!! code: %d, arg: (%s, %s)", result, W2str(&w), X2str(&x)); return result; } result = hogera(&y, &z); if (result != E_OK) return result; return E_OK; }
Erlangで同じコードを書くと、Erlangは途中でreturn
することができないので、こうなる。
-spec f() -> {ok, z()} | {error, term()}. f() -> case hoge() of {ok, W} -> case fuga(W) of {ok, X} -> case piyo(W, X) of {ok, Y} -> case hogera(Y) of {ok, Z} -> {ok, Z}; {error, Reason} -> {error, Reason} end; {error, Reason} -> %% piyoのエラーはログを出す log("error in piyo !!! code: ~p, arg: (~p, ~p)", [Reason, W, X]), {error, Reason} end; {error, Reason} -> {error, Reason} end; {error, Reason} -> {error, Reason} end.
ネストが深いし、関数の呼び出しとエラー処理が離れていて読みづらい。
return
のある言語でも、single-entry single-exitルールで書いていたり、JavaScriptのいわゆるコールバック地獄になったりすると、同じようになる。
エラー処理が離れている点については、エラー処理を先に書くという手がある。
-spec f() -> {ok, z()} | {error, term()}. f() -> case hoge() of {error, Reason} -> {error, Reason}; {ok, W} -> case fuga(W) of {error, Reason} -> {error, Reason}; {ok, X} -> case piyo(W, X) of {error, Reason} -> %% piyoのエラーはログを出す log("error in piyo !!! code: ~p, arg: (~p, ~p)", [Reason, W, X]), {error, Reason}; {ok, Y} -> case hogera(Y) of {error, Reason} -> {error, Reason}; {ok, Z} -> {ok, Z} end end end end.
見た目も元のC言語のコードに近い。 ただし、ネストの深さは変わらない。 C言語では上から下に読めるが、Erlangでは左上から右下に読むことになる。
エディタがこのように斜めに表示してくれれば、ネストの深さの問題が解決する。