gen_eventと継承のお話

Elixir Advent Calendar 2014 18日目。

ElixirもOTPの恩恵を受けられるわけですが、具体的にOTPで何ができるの？というと、僕が引き合いに出すのは ```gen_server``` , ```gen_event``` , ```gen_fsm``` です。
この中で、今回は ```gen_event``` にフォーカスして、Elixirならではの観点から紹介してみたいと思います。

# gen_event

```gen_event``` は、Erlang/OTPに含まれる、標準的な振る舞いの一つです。
```gen_server``` ほどには利用されていないため、OTP初学者にはあまり知られていないようです。
```gen_event``` は ```gen_server``` と振る舞いが似ていますが、根本的な部分で異なります。
```gen_server``` の場合、全てのコールバックが、 ```gen_server``` プロセスのコンテキストのもとで実行されるのに対し、
```gen_event``` の場合、イベントハンドラに向けたコールバックのみ、イベントマネージャープロセスのコンテキストのもとで実行されます。
ひとつ、Elixirにおける ```gen_event``` の例を示します。

まず、イベントハンドラを定義します。

```elixir
defmodule BroadcastEventHandler do
  use GenEvent

  def init(_) do
    {:ok, []}
  end

  def handle_event({:broadcast, x}, broadcasts) do
    {:ok, [x|broadcasts]}
  end

  def handle_call(:broadcasts, broadcasts) do
    {:ok, broadcasts, []}
  end
end
```

次に、プロセスを ```gen_event``` にリンクします。

```elixir
{:ok, pid} = :gen_event.start_link
```

そして、先ほど定義したイベントハンドラを登録します。

```elixir
:gen_event.add_handler(pid, BroadcastEventHandler, [])
```

ここで、メッセージをブロードキャストしてみましょう。

```elixir
:gen_event.notify(pid, {:broadcast, "Hello gen_event"})
```

コールしてみましょう。メッセージは1回目のコールでは取得できますが、2回目では空になることに注目して下さい。

```elixir
:gen_event.call(pid, BroadcastEventHandler, :broadcasts)
#=> ["Hello gen_event"]
:gen_event.call(pid, BroadcastEventHandler, :broadcasts)
#=> []
```

イベントハンドラの工夫次第で、様々なイベントドリブンなコールバックが実現できますね。
ここまで、 ```gen_event``` の簡単な紹介でした。

# Elixirにおける継承

Elixirには、 ```defoverridable``` という修飾子があります（ちなみに、マクロで実装されています）。
これは、Tupled Listで指定した任意の関数を、継承可能とするものです。
いきなりOOPの要素が出てきて面白いですね。

継承の例を挙げてみます。

```elixir
defmodule Bike do
  defmacro __using__(_opts) do
    quote do
      def handle do
        1
      end

      def wheel do
        2
      end

      defoverridable [handle: 0, wheel: 0]
    end
  end
end

defmodule Trike do
  use Bike

  def handle do
    super
  end

  def wheel do
    1 + super
  end
end
```

```elixir
Trike.handle
#=> 1
Trike.wheel
#=> 3
```

Bikeモジュールのhandle関数とwheel関数が、Trikeモジュールに継承されました。
ちなみに、 ```defoverridable``` で指定していない関数を継承しようとすると、モジュール定義のタイミングでエラーになります。


# Elixir標準関数の中のdefoverridable

こういう仕組みがあると、Elixirの標準関数を継承して独自のモジュールを作る欲望に駆られますよね。
そういうわけで、Elixirの処理系を洗ってみました。
結果わかったことですが、 ```defoverridable``` 指定されているのは、 ```Kernel``` , ```Dict``` , ```gen_event``` だけでした。


# gen_eventに戻る

本記事の冒頭で出した ```gen_event``` の例は、この継承を使って実現しています。
各関数を、オリジナルの ```gen_event``` のソースコードを使って表現すると、以下のようになります。

```elixir
defmodule BroadcastEventHandler do
  use GenEvent

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

  def handle_event(_event, state) do
    {:ok, state}
  end

  def handle_call(msg, state) do
    case :random.uniform(1) do
      1 -> exit({:bad_call, msg})
      2 -> {:remove_handler, :ok}
    end
  end
end
```

このままではロクに動かないわけですが、それは継承して使うことが前提となっているからですね。
冒頭と同じコードを再掲します。ぜひ見比べてみて下さい。

```elixir
defmodule BroadcastEventHandler do
  use GenEvent

  def init(_) do
    {:ok, []}
  end

  def handle_event({:broadcast, x}, broadcasts) do
    {:ok, [x|broadcasts]}
  end

  def handle_call(:broadcasts, broadcasts) do
    {:ok, broadcasts, []}
  end
end
```

# まとめ

```gen_event``` を、Elixirの継承と絡めてご紹介しました。
継承は、 ```gen_event``` に限らず、色々と応用できそうですね。
明日は @ma2ge さんです。