16
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

gen_eventと継承のお話

Last updated at Posted at 2014-12-18

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

16
14
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
16
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?