Elixir
ElixirDay 18

gen_eventと継承のお話

More than 1 year has passed since last update.

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_eventgen_server と振る舞いが似ていますが、根本的な部分で異なります。
gen_server の場合、全てのコールバックが、 gen_server プロセスのコンテキストのもとで実行されるのに対し、
gen_event の場合、イベントハンドラに向けたコールバックのみ、イベントマネージャープロセスのコンテキストのもとで実行されます。
ひとつ、Elixirにおける gen_event の例を示します。

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

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 にリンクします。

{:ok, pid} = :gen_event.start_link

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

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

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

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

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

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

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

Elixirにおける継承

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

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

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
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 のソースコードを使って表現すると、以下のようになります。

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

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

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 さんです。