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

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

# 概要

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

# テクニック

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

**Bad**

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

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

**Good**

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

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

参考： [inaka/erlang_guidelines#more-smaller-functions-over-case-expressions](https://github.com/inaka/erlang_guidelines#more-smaller-functions-over-case-expressions)

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

**Bad**

```erlang
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**

```erlang
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**

```erlang
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**

```erlang
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リスト

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

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

```erlang
1> do_something(A,B,C).
{error, {error, ok, error}}
% do_a/1 と do_c/1 が error
```

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

**Good**

```erlang
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リスト

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

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