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, メタプログラミング)の学習に大変役に立ちました。ありがとうございます。
Elixirとのタワムレ
みなさん如何お過ごしでしょうか? 私は一週間遅れてお盆休みを取ったので、実家に帰省中です。 今日は瀬戸内芸術祭の会場の一つである豊島に行ってきました。
心臓の音を聞いていたマン pic.twitter.com/yrbD98Oq2p
— Trs (@TrsNium) August 19, 2019
色々な作品があって、とても興味深かったです。
なんでElixirなん
さて本題ですが、elixirを触った理由は以下の理由があります。
Erlangは以前に勉強していたのですが、OTPやErlang VMの軽量プロセスの恩恵を感じられるような物を作ることができませんでした。 そこで今回はElixirを使ってみて、Erlangとのシンタックスなどの違い、軽量プロセス感の通信を使ったアプリケーションを作ってみてElixir, Erlang VMの良さに触れてみたいと考えました。 これらを考えた結果今回は、特定の深さ以下の任意のディレクトリ以下を走査し、ファイルの中に特定の単語があるか調べるプログラムを組んでみました。 github.com
アクターモデル
Erlang VMではプロセス間のメッセージ通信にアクターモデルの概念を念頭に行います。 僕は説明が上手くないので詳しくはwikipediaの基本概念部分を参照して欲しいです。 今回私が作成物のプロセスは以下の図のようにメッセージ通信をしています。
順を追って説明すると、
まずワーカープロセスがスケジューラーに:ready
というメッセージを送信します。
:ready
を受け取ったスケジューラーは、送り主のワーカープロセスに対し:work
, :idle
, :shutdown
の内どれかのメッセージを送り返します。
:work
はスケジューラーの中に実行すべきキューがまだある場合に送信されます。:work
を受け取ったワーカープロセスは勝利した結果を:answer
というメッセージで返信します。その後ワーカープロセスは、また:readyを送信するようになります。
:idle
は実行すべきキューは無いが、プロセスを待たせたいときに使い、:idle
を受け取ったワーカープロセスはn msほどスリープし:ready
を再び送信します。
:shutdown
は実行すべきキューが空かつ待ちが必要のないときに送信され、受け取ったワーカープロセスはexitします。
schedulerプロセスとワーカープロセスが相互に通信をしあい、溜まったキューの処理を進めていきます。 また複数のワーカープロセスを使うことでErlang VMがコンピューターリソースを効率的に使用します。
コンピューターリソースを全部使い切り太郎できている、最高 pic.twitter.com/2GS2cdS1yw
— Trs (@TrsNium) August 18, 2019
まとめ
オブジェクト指向は状態を元に振る舞いが変化するならば、アクターモデルは受け取った状態を元に振る舞いを変えるというような感じでしょうか。 どことなく似ているようで全然違うのですが、新しい概念に触れられて勉強になりました。 またErlang VMがワーカープロセスの数を増やせば増やすほどコンピュータリソースを食っていき、カツカツになっていく様を見るのは楽しかったです。 次はGenServerなんかに触れられたらなぁと思います。
参考
プロセス間のメッセージやり取りはプログラミングElixir
を参考にさせていただきました。
ありがとうございます。