LoginSignup
5
1

More than 5 years have passed since last update.

Erlangでモナド

ビヘイビアを使ってmonad型クラスのようなものを提供。monadのインスタンスを作りたい人はreturn/1とbind/2を用意すればよいようにしている。

monad.erl
-module(monad).

-export([flatten/2, map/3, sequence/2, mapM/3]).

-type m(_A) :: term().

-callback return(A) -> m(A) when A :: term().
-callback bind(m(A), fun((A) -> m(B))) -> m(B).

-spec flatten(module(), m(m(A))) -> m(A)  when
      A :: term().
flatten(Instance, Mmx) ->
    Instance:bind(Mmx, fun(M) -> M end).

-spec map(module(), fun((A) -> B), m(A)) -> m(B) when
      A :: term(),
      B :: term().
map(Instance, F, M) ->
    Instance:bind(M, fun(X) -> Instance:return(F(X)) end).

-spec sequence(module(), list(m(A))) -> m(list(A)) when
      A :: term().
sequence(Instance, []) ->
    Instance:return([]);
sequence(Instance, [M | Ms]) ->
    Instance:bind(M, fun(Hd) ->
    Mtl = sequence(Instance, Ms),
    Instance:bind(Mtl, fun(Tl) ->
    Instance:return([Hd | Tl])
    end) end).

-spec mapM(module(), fun((A) -> m(B)), list(A)) -> m(list(B)) when
      A :: term(),
      B :: term().
mapM(Instance, F, Xs) ->
    sequence(Instance, lists:map(F, Xs)).

モナドのインスタンス例

resultモナド

{ok, _}または {error, _} のような形をしたデータ型をresult型と呼ぶことにする。
OCamlでいう('a, exn) result 型、Scalaでいう Try[A] 型。
Erlangではこういう形のデータを想定した処理がたくさんあるらしいので、モナディックに結合したいので↑のmonadビヘイビアを使ってモナドにする。

result.erl
-module(result).

-behavior(monad).

-export([return/1, bind/2]).
-export_type([result/1]).

-type result(A) :: {ok, A} | {error, term()}.

-spec return(A) -> result(A) when A :: term().
return(X) -> {ok, X}.

-spec bind(result(A), fun((A) -> result(B))) -> result(B) when
      A :: term(),
      B :: term().
bind({error, Reason}, _F) -> {error, Reason};
bind({ok, X}, F) -> F(X).

使用例:

Erlangにはまだdo構文相当がないようなので、以下のようにbindでつなげる。

sample.erl
main() ->
  result:bind( find_parameter(),    fun(Param) ->
  result:bind( take_user_id(Param), fun(UserId) ->
  result:bind( find_user(UserId),   fun(User) ->
  result:return(User)
  end) end) end).

(find_parameter/0, take_user_id/1, find_user/1 はそれぞれ失敗するかもしれない架空の関数)
do構文 (Scalaのfor) がわかる人は以下のように心の目で置き換えて読むのがよい。

sample.erl
main() ->
  result:do
    Param  <- find_parameter(),
    UserId <- take_user_id(Param),
    User   <- find_user(UserId),
    return (User)
  end.
5
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
5
1