LoginSignup
6
1

More than 1 year has passed since last update.

return文がないErlangでネストを深くしないためのテクニック

Last updated at Posted at 2020-12-29

Erlang/OTP 25.0以降ではmaybe式が導入されているため、そちらを利用しましょう。本記事の対象読者は、それ以前のバージョンの利用者です。

概要

Erlangには他のプログラミング言語で言う所のreturn文がありません。
returnがないため、当然Early returnというテクニックが使えません。
Early returnは、異常が見つかった場合などに関数の早いタイミングで処理を打ち切ることができるため、ネストが深くなる前に関数を脱出することができます。
この記事ではEarly returnが使えないErlangでネストを深くしないためのテクニックついて説明します。

テクニック

1. case式よりも、多くの小さな関数

Bad

do_something(Arg) ->
    case Arg of
        a ->
            do_a();
        b ->
            do_b()
    end.

なるべく関数のパターンマッチで済ませましょう。

Good

do_something(a) ->
    do_a();
do_something(b) ->
    do_b().

when によるガードも同様です。

参考: inaka/erlang_guidelines#more-smaller-functions-over-case-expressions

2. まとめてパターンマッチ

Bad

do_something(A, B, C) ->
    case do_a(A) of ->
        ok ->
            case do_b(B) of
                ok ->
                    case do_c(c) of
                        ok ->
                            ok;
                        error ->
                            error
                    end;
                error ->
                    error
            end;
        error ->
            error
    end.

最悪ですね。 タプルを使って以下のように書き直せます。

Good

do_something(A, B, C) ->
    case
        {
            do_a(A)
            do_b(B)
            do_c(C)
        {
    of ->
        {ok, ok, ok} ->
            ok;
        _ ->
            error
    end.

3. 関数分割

まとめてパターンマッチの例では do_* 関数の呼び出し間で依存関係がありませんでした。しかし、依存関係があった場合にはまとめてパターンマッチが使えません。

Bad

do_something(A, B, C) ->
    case do_a(A) of ->
        {ok, ResultA} ->
            case do_b(B, ResultA) of
                {ok, ResultB} ->
                    case do_c(C, ResultB) of
                        {ok, _ResultC} ->
                            ok;
                        error ->
                            error
                    end;
                error ->
                    error
            end;
        error ->
            error
    end.

この例には関数分割を適用してみます。

Good

do_something(A, B, C) ->
    case do_a(A) of ->
        {ok, ResultA} ->
            do_something_1(ResultA, B, C);
        error ->
            error
    end.


do_something_1(ResultA, B, C) ->
    case do_b(ResultA, B) of ->
        {ok, ResultB} ->
            do_something_2(ResultB, C);
        error ->
    end.

do_something_2(ResultB, C)
    case do_c(ResultB, C) of ->
        {ok, _ResultC} ->
            ok
        error ->
            error
    end.

${API関数名}_${N}という命名法則によって元の大きな関数を分割することで、深いネストを解消できました。
関数一つあたりのスコープが小さくなってることも良いですね。

4. Errorリスト

実は、まとめてパターンマッチでのBadGoodでは do_a/1error 時に do_b/1 を実行するかどうか(do_b/1do_c/1も同様)という違いがあります(関数分割も後続の関数は実行されせませんね)。
`まとめてパターンマッチでは全ての関数が実行されるため、それぞれがエラーだったかどうかを返せます。

do_something(A, B, C) ->
    case
        {
            do_a(A)
            do_b(B)
            do_c(C)
        {
    of ->
        {ok, ok, ok} ->
            ok;
        Other ->
            {error, Other}
    end.
1> do_something(A,B,C).
{error, {error, ok, error}}
% do_a/1 と do_c/1 が error

では、それぞれのdo_*がエラーだったかどうかも返したいが、do_*間で依存関係がある場合はどうすればいいでしょうか?
そんな時はErrorリストがいいでしょう.

Good

do_something(A, B, C) ->
    Error0 = [],

    {ResultA, Error1} =
        case do_a(Param) of
            {ok, ResultA} ->
                {ResultA, Error0}
            {error, Reason0} ->
                [{error, Reason0} | Error0]
        end,

    {ResultB, Error2} =
        case do_b(B, ResultA) of
            {ok, ResultB} ->
                {ResultB, Error1}
            {error, Reason1} ->
                [{error, Reason1} | Error1]
        end,

    {_ResultC, Error3} =
        case do_c(C, ResultB) of
            {ok, ResultC} ->
                {ResultC, Error1}
            {error, Reason2} ->
                [{error, Reason2} | Error2]
        end,

    case Error3 of
        [] ->
            ok;
        Error3 ->
            {error, Error3}
    end.

まとめ

Erlangのネストを深くしないために気をつけているテクニックとして

  1. case式よりも、多くの小さな関数
  2. まとめてパターンマッチ
  3. 関数分割
  4. Errorリスト

を紹介しました。油断すると一瞬でネストが深くなってしまう言語なので気をつけていきたいですね。

この記事中に出てくる用語は、筆者が適当につけたものですので、既に名前がありましたらコメントや編集リクエストでお教えください。
他にも有用なテクニックがあった場合にもご教授いただけると幸いです。

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1