erlando - ErlangでMaybeモナドとdo記法を使う 

ErlangでHaskell風の **Maybeモナド** と **do記法** を使う方法。エラーチェックなどを繰り返し行うコードが簡潔に記述できる。RabbitMQの開発者が公開した [erlando] (https://github.com/rabbitmq/erlando) で実現する。

erlandoにはListモナドやStateモナドなども含まれており、もちろん自分で新たにモナドを書くこともできる。また一部のモナドではMonadPlus型も実装されており `guard/2` 関数などが使える。さらに、関数の部分適用を読みやすく記述できる「cut」や、関数のインポート時に関数名を変更することで名前の衝突を避ける「import as」という機能もある。


# maybe(A) 型

Erlang/OTPでは処理が失敗するかもしれない関数は、失敗したことを「例外」ではなく、「戻り値」で表現する。例えば `lists:keyfined/3` ではキーが見つからない場合 `false` を、 `file:open/2` では `{error, Reason}` を返す。

```erl
> lists:keyfind(a, 1, [{a, alice}, {b, bob}]).
{a,alice}
> lists:keyfind(c, 1, [{a, alice}, {b, bob}]).
false
> file:open("a", [read,raw]).
{error,enoent}
```

maybe(A) 型はこれを汎用化したもので、HaskellやScalaなどの言語では標準ライブラリに入っている。処理の成功時は値を `{just, _}` で包んで返し、失敗時は単に `nothing` を返す。

```walkline.erl
-type maybe(A) :: {just, A} | nothing.
```

ここでは例として **Learn You a Haskell for Great Good!** の [**Walk the Line**] (http://learnyouahaskell.com/a-fistful-of-monads#walk-the-line) を Erlang に移植してみる。養魚場で働くピエールが綱渡りに挑戦するが、バランス棒の左右にとまる鳥に邪魔されるという内容だ。棒の左右にとまった鳥の数の差が３以内であればピエールはバランスを保ち、４以上になるとバランスを崩して落ちてしまう。

なお Learn You a Haskell for Great Good! の日本語翻訳版「 **すごいHaskell たのしく学ぼう！** 」は [こちら] (http://www.amazon.co.jp/dp/4274068854) から購入できる。

まずは型定義。鳥の数を `birds` 型、バランス棒の状態を `pole` 型として定義する。

```walkline.erl
-type birds() :: integer().
-type pole() :: {birds(), birds()}.
```

`pole` タプルの１つ目の値は棒の左側にとまった鳥の数、２つ目の値は右側の鳥の数を表す。

次は関数のスペック。 `land_left/2` は棒の左側に鳥が着陸、 `land_right/2` は右側に着陸することを表す。

```walkline.erl
-spec land_left(birds(), pole()) -> maybe(pole()).
-spec land_right(birds(), pole()) -> maybe(pole()).
```

１つ目の引数は鳥の数で、正の値なら鳥がとまったと考え、負の値なら鳥が飛んだと考える。２つ目の引数は現在の棒の状態を表す。検査の結果、棒の左右の鳥の差が３以内なら `{just, pole()}` を、それ以外ならバランスを崩して落ちたとして `nothing` を返す。

```erl
> walkline:land_left(1, {0, 0}).
{just,{1,0}}
> walkline:land_left(4, {0, 0}).
nothing
```

これらの関数の実装は以下の通り。

```walkline.erl
land_left(N, {Left, Right}) ->
    case abs((Left + N) - Right) < 4 of
        true ->
            {just, {Left + N, Right}};
        false ->
            nothing
    end.

land_right(N, {Left, Right}) ->
    case abs(Left - (Right + N)) < 4 of
        true ->
            {just, {Left, Right + N}};
        false ->
            nothing
    end.
```


# モナドなしで普通に書いてみる

最初に鳥が棒の左に２羽とまり、次に右に２羽とまり、最後に左に１羽とまるケースを実装する。失敗する可能性を考慮しながら書くと以下のようになる。

```walkline.erl
-spec routine1() -> maybe(pole()).
routine1() ->
    case {just, {0,0}} of
        {just, Start} ->
            case land_left(2, Start) of
                {just, First} ->
                    case land_right(2, First) of
                        {just, Second} ->
                            land_left(1, Second);
                        nothing ->
                            nothing
                    end;
                nothing ->
                    nothing
            end;
        nothing ->
            nothing
    end.
```

Erlangでは典型的なコードだ。実行してみよう。

```erl
> walkline:routine1().
{just,{3,2}}
```

綱渡りに成功したので `just` が返ってきた。


# Maybeモナドとdo記法

モナドを使って上のコードを簡潔にしよう。 `routine1/0` は条件判定と値の引き継ぎ方（ `{just, Start}` など）は各ステップで同じで、それを繰り返している。このパターンを抽象化すればよい。

ここで活躍するのが Maybeモナドだ。erlandoでは `maybe_m` モジュールとして用意されていて、そこでは、連結関数 `'>>='/2` や、注入関数 `return/1` などが定義されている。

```maybe_m.erl
-behaviour(monad).

-type(monad(A) :: {'just', A} | nothing).

'>>='({just, X}, Fun) -> Fun(X);
'>>='(nothing,  _Fun) -> nothing.

return(X) -> {just, X}.
fail(_X)  -> nothing.
```

連結関数 '>>='/2 はmaybe(A) 型のモナド値を受け取り、値がもし `{just, 値}` だったらその値に関数 `Fun` を適用する。 `Fun` は `land_left/2` のように「普通の値を受け取ってなんらかの計算を行い、モナド値を返す」関数だ。また、もし受け取ったモナド値が `nothing` だったら関数の適用はせず `nothing` を返す。関数を適用しようにも値がないので自然な振る舞いといえる。

注入関数 `return/1` は普通の値を受け取って `{just, _}` で包む関数だ。一般的なプログラミング言語の `return` では処理を打ち切って値を返すために使うが、モナドの `return/1` は処理を打ち切らない。この２つは名前がたまたま同じだけで、全くの別物であることに注意してほしい。

`fail/1` は失敗を表す値を返す。

HaskellのMaybeモナドでも同じ関数が定義されている。`routine1/0` と同じ処理をHaskellで書くとこうなる。

```haskell
> return (0,0) >>= landLeft 2 >>= landRight 2 >>= landLeft 1
Just (3,2)
```

`>>=` による関数の連鎖の中で、モナド値が左から右に流れていくイメージだ。

ErlangでもMaybeモナドの関数と、無名関数（ `fun() -> ... end` ）を組み合わせれば動くコードが書ける。しかしErlangは関数の中置構文、遅延評価、関数の部分適用などの機能を持たないので、非常に読みづらくなってしまう。そこで専ら「do記法」というHaskellのモナド専用の糖衣構文を真似たものを使う。

do記法はもちろんErlang標準の構文ではない。erlandoではparse transformというErlangのメタプログラミング機能を使ってdo記法を実現している。そのため、do記法で書いた式は、コンパイル時に普通のErlang式に変換される。

先の `routine1/0` をdo記法で書き直そう。 `-compile` 文で `do` を使うことを宣言する。処理は `do` ブロックの中に書く。

```walkline.erl
-compile({parse_transform, do}).

-spec routine2() -> maybe(pole()).
routine2() ->
    do([maybe_m ||
           Start <- return({0, 0}),
           First <- land_left(2, Start),
           Second <- land_right(2, First),
           land_left(1, Second)
       ]).
```

とても読みやすくなった。実行してみよう。

```erl
> walkline:routine2(). 
{just,{3,2}}
```

`routine3/0` は途中で失敗するケースだ。

```walkline.erl
-compile({parse_transform, do}).

-spec routine3() -> maybe(pole()).
routine3() ->
    do([maybe_m ||
           Start <- return({0, 0}),
           First <- land_left(1, Start),
           Second <- land_right(4, First),
           Third <- land_left(-1, Second),
           land_right(-2, Third)
       ]).
```

```erl
> walkline:routine3().
nothing
```

なお、`routine2/0` をdo構文を使わずに Erlang 標準の構文で書くとこのようになる。読みにくいが、モナドの動作を理解するには役立つ。 `routine2/0` もコンパイル時にこれに近い内部表現に変換されているはずだ。

```walkline.erl
-import(maybe_m, ['>>='/2, return/1]).

-spec routine4() -> maybe(pole()).
routine4() ->
    '>>='(return({0, 0}),
          fun(Start) ->
                  '>>='(land_left(2, Start),
                        fun(First) ->
                                '>>='(land_right(2, First),
                                      fun(Second) ->
                                              land_left(1, Second)
                                      end)
                        end)
          end).
```


# errorモナド

erlandoのerrorモナド `error_m.erl` はmaybeモナドにそっくりだが、モナド値としてMaybe型を受け取る代わりに、OTPでよく使われている `ok | {ok, _} | {error, _}` を受け取る。

以下のコードは https://github.com/rabbitmq/erlando のREADMEより引用

## 使用前：Erlang標準の書き方

```erlang
write_file(Path, Data, Modes) ->
    Modes1 = [binary, write | (Modes -- [binary, write])],
    case make_binary(Data) of
        Bin when is_binary(Bin) ->
            case file:open(Path, Modes1) of
                {ok, Hdl} ->
                    case file:write(Hdl, Bin) of
                        ok ->
                            case file:sync(Hdl) of
                                ok ->
                                    file:close(Hdl);
                                {error, _} = E ->
                                    file:close(Hdl),
                                    E
                            end;
                        {error, _} = E ->
                            file:close(Hdl),
                            E
                    end;
                {error, _} = E -> E
            end;
        {error, _} = E -> E
    end.

make_binary(Bin) when is_binary(Bin) ->
    Bin;
make_binary(List) ->
    try
        iolist_to_binary(List)
    catch error:Reason ->
            {error, Reason}
    end.
```


### 使用後：errorモナドを使用

```erlang
write_file(Path, Data, Modes) ->
    Modes1 = [binary, write | (Modes -- [binary, write])],
    do([error_m ||
        Bin <- make_binary(Data),
        Hdl <- file:open(Path, Modes1),
        Result <- return(do([error_m ||
                             file:write(Hdl, Bin),
                             file:sync(Hdl)])),
        file:close(Hdl),
        Result]).

make_binary(Bin) when is_binary(Bin) ->
    error_m:return(Bin);
make_binary(List) ->
    try
        error_m:return(iolist_to_binary(List))
    catch error:Reason ->
            error_m:fail(Reason)
    end.
```

その差は歴然としている。なお、このように `do` ブロックを入れ子にすることで、内側の `do` ブロックでファイルを書き込んでいる最中にエラーが起こっても、外側の `do` ブロックが確実にファイルをクローズできる。


# 導入方法

ビルドツールの [rebar] (https://github.com/rebar/rebar) を使うとよいだろう。

プロジェクトの `rebar.config` に依存関係を追加する。

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

`get-deps` でerlandoを取得し、 `compile` でプロジェクト全体をビルドする。

```
$ ./rebar get-deps
$ ./rebar compile
```

Erlang VMではerlandoをパスに追加すればOK。

```
$ erl -pz ebin/ -pz deps/erlando/ebin/
```

rebarの使い方は、こちらで紹介されている。

**とり日記 -- 2012/07/10(火)	 Erlang解説(19) - rebar -**
http://www.avian-net.jp/blog/sakada/053