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
の例を示します。
まず、イベントハンドラを定義します。
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 さんです。