ElixirとOTP周りの関係

Elixir Advent Calendar 2014 22日目。

私の前回の記事は ```gen_event``` に関するものでした。
せっかくなので、OTP全般について、「Elixirの場合はどう書くのか」「ElixirはErlangとどう違うのか」といった観点で見ていきましょう。


# gen_server

```gen_server``` については、 @k1compolete さんが2年以上前に作られた資料が、とても端的にまとめられています。
http://s.testerhome.com/k1complete/development-appwithelixir

```gen_server``` には、Elixirならではの違いは特に見られません。
どちらかと言うと、書き方の違いや、その言語自体の処理系の実装の違いとなるでしょう。
（Erlang/OTPでは「振る舞い」として指定するのに対し、Elixirでは継承を使うなど。）

そこで、日本語での解説が少ない話題を一つ。
```gen_server``` の再起動ストラテジーには ```simple_one_for_one``` , ```one_for_one``` , ```rest_for_one``` , ```one_for_all``` がありますが、Elixirでは実際どのように指定するのでしょうか。

これらは、スーパーバイザーのモジュール定義内で指定します。
以下にサンプルコードを示します。

```elixir
defmodule MyApp.Server do
  use GenServer

  def start_link do
    GenServer.start_link(MyApp.Server, [], name: MyApp)
  end

  def init(stack) do
    {:ok, stack}
  end

  def handle_call(:pop, _from, [h|stack]) do
    {:reply, h, stack}
  end

  def handle_cast({:push, new}, stack) do
    {:noreply, [new|stack]}
  end
end


defmodule MyApp.Supervisor do
  use Supervisor

  def start_link do
    Supervisor.start_link(__MODULE__, :ok)
  end

  def init(:ok) do
    children = [worker(MyApp.Server, [], restart: :temporary)]
    supervise(children, strategy: :rest_for_one)
  end
end
```

上記では、 ```rest_for_one``` を指定しました。
```MyApp.Supervisor.init(:ok)``` をコールすると、再起動ストラテジーがセットされます。

# gen_event

Elixirの ```GenEvent``` は、Erlang/OTPの ```gen_event``` に対する上位互換となっており、ストリーム形式のイベントにも対応しています。
例を見てみましょう。

```ex
{:ok, pid} = GenEvent.start_link()
stream = GenEvent.stream(pid)

spawn_link fn ->
  for event <- stream do
    IO.puts "Got: #{IO.inspect(event)}"
  end
end

GenEvent.notify(pid, :hello)
GenEvent.sync_notify(pid, :world)
```

```GenEvent.notify/2``` をコールすると、イベントマネージャーに通知され、Erlang/OTPの ```Module:handle_event/2``` がイベントハンドラごとにコールされます。
```GenEvent.notify/2``` は非同期であり、通知が送られると、即座に return します。
これに対して、 ```GenEvent.sync_notify/2``` は同期処理を行います。

イベントハンドラを定義していない点に注目して下さい。
イベントマネージャーに対するイベントハンドラの登録は、ストリームを受信した際に、動的に行われます。
また、ストリームを行うプロセスが死んだ場合、イベントハンドラはイベントマネージャーから削除されます。
これらの挙動により、ストリームの安全性が保証されています。

通常、 ```GenEvent``` を利用する際はイベントハンドラによりコールバックを定義する必要がありますが、ストリームの場合はそれ無しで動作するので、大変便利です。


# gen_fsm

```gen_fsm``` のElixir実装である ```GenFSM``` は、v0.12.5で廃止となりました。


# Agent

廃止された ```GenFSM``` に代わって実装されたのが、 ```Agent``` です。
Elixirのプロセス、アクターモデル、 ```gen_fsm``` で実現するステートマシーン、これらを昇華させ、エージェントモデルを実現する振る舞いが、Elixirの ```Agent``` です。
（そういう意味では、eXAT - The erlang eXperimental Agent Tool に近いコンセプトかもしれません。）

```Agent``` は、 ```GenServer``` を継承して作られています。
ハンドラとしては ```call``` , ```cast``` があり、 ```call``` には ```get``` , ```get_and_update``` , ```update``` , ```stop``` , (任意の)msg があります。
ハンドラ以外には、 ```init``` , ```terminate``` , ```code_change``` があります。 ```code_change``` は、Erlangではおなじみの、ホット・コード・スワッピングのための機構です（※未確認）。
これらが、Elixir上ではAPIとして機能し、プロセス間でのステート遷移をサポートします。

Elixirのコード内では、 ```Mix.TasksServer``` で使われています。以下のような実装になっています。

```elixir
defmodule Mix.TasksServer do
  @moduledoc false

  def start_link() do
    Agent.start_link(fn -> HashSet.new end, name: __MODULE__)
  end

  def clear() do
    update fn _ -> HashSet.new end
  end

  def run(tuple) do
    get_and_update fn set ->
      {not(tuple in set), Set.put(set, tuple)}
    end
  end

  def put(tuple) do
    update &Set.put(&1, tuple)
  end

  def delete_many(many) do
    update &Enum.reduce(many, &1, fn x, acc -> Set.delete(acc, x) end)
  end

  defp get_and_update(fun) do
    Agent.get_and_update(__MODULE__, fun, 30_000)
  end

  defp update(fun) do
    Agent.update(__MODULE__, fun, 30_000)
  end
end
```

agent.ex には、Module Docが丁寧に書かれています。詳しく知りたい方は、ぜひ読んでみて下さい。


# Task

```Task``` モジュールは、非同期でデータの処理を行うプロセス同士のスワッピングに便利な機能を提供します。
```Task``` の定義はこのように行います。

```elixir
task = Task.async(fn -> do_some_work() end)
res  = do_some_other_work()
res + Task.await(task)
```

```Task``` の定義ができたら、リモートのノードでスーパーバイザーを ```start_link``` します。

```elixir
Task.Supervisor.start_link(local: :tasks_supervisor)
```

クライアント側では、このようにコールします。

```elixir
Task.Supervisor.async({:tasks_supervisor, :remote@local}, fn -> do_work() end)
```

RPCの実装が簡単になりますね。

# まとめ

少し取り留めの無い感じになってしまいましたが、OTP周りに関するElixirにおける特徴を挙げてみました。
Elixirからは、Erlang/OTPを踏襲しつつも、更に発展させていこうというコンセプトが見受けられますね。

明日は @niku さんです。