Elixirでベクトルクロックを実装した
Elixirでベクトルクロックを実装致した。 リポジトリは下記にあります。(readmeは後々追加予定)
使い方は下記の通りです。
# define your processing. defmodule Tick.Example do use Tick.StateMachine def increment(n) do tick do n + 1 end end end # make a config to send other nodes. config = Tick.Config.new(:my_name, [{:my_name, nil}, {:other_node_name, :other_node_name@address}]) # get a spec to generate supervisor. spec = Tick.Example.child_spec(config) Supervisor.start_link([spec], strategy: :one_for_one) # execute block inside `tick`, and send message other nodes. Tick.Example.increment(0)
記述量も少なく使いやすいものが出来たのではないかと思っています。 実際に動かしたところ
ブログ用 pic.twitter.com/2Y8DhyAoYK
— Trs (@TrsNium) August 29, 2019
以下説明です。
実装したコード
実装するにあたり、ライブラリ使用者の負担を減らすため少ないコード量で利用できるように心がけました。 コード記述量を減らすためにメタプログラミングでコードを生成する事により、共通的なコードを実装する手間を排除することができました。 メタプログラミングの実装は以下のようになっています。
defmodule Tick.StateMachine do defmacro __using__(_opts) do quote location: :keep do def child_spec(%Tick.Config{}=config) do init_opts = %Tick.Server.State{} |> Map.put(:current_state, Tick.Server.State.init_current_state(config)) |> Map.put(:config, config) Supervisor.child_spec({Tick.PeerSupervisor, init_opts}, id: Tick.PeerSupervisor) end defmacrop tick(do: block) do quote do result = unquote(block) Tick.Server.finish_process() result end end end end end
__using__
マクロ内で定義したものはモジュールの定義になるため、__using__
内で書いた関数などは__CALLER__
モジュールに展開されます。
つまりuse Tick.StateMachine
と書くだけで、child_spec
, tick
関数を利用することができます。
次にステートを保持&他ノードへのメッセージの送信をする部分です。
GenServer
を用いステートを管理し、メッセージはTask.Supervisor
を用い送信しています。
ファイルでいえばここ
特に難しいことはしていないので、説明は割愛で...
感想
やはりElixirで色々実装するのは楽しい、これに尽きます。 簡単にメタプログラミングをすることができ、オブジェクトを作る感覚でプロセスを作ることが出来る...。 今までにないプログラミングの哲学を見ているようで、Elixirでコードを書くことが目的になりそうです。
参考
elixirでのraftの実装になりますが、elixirの(OTP, メタプログラミング)の学習に大変役に立ちました。ありがとうございます。