なんもわからんな

Elixirでベクトルクロックを実装した

Elixirでベクトルクロックを実装致した。 リポジトリは下記にあります。(readmeは後々追加予定)

github.com

使い方は下記の通りです。

# 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)

記述量も少なく使いやすいものが出来たのではないかと思っています。 実際に動かしたところ

以下説明です。

実装したコード

実装するにあたり、ライブラリ使用者の負担を減らすため少ないコード量で利用できるように心がけました。 コード記述量を減らすためにメタプログラミングでコードを生成する事により、共通的なコードを実装する手間を排除することができました。 メタプログラミングの実装は以下のようになっています。

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, メタプログラミング)の学習に大変役に立ちました。ありがとうございます。

github.com

Elixirとのタワムレ

みなさん如何お過ごしでしょうか? 私は一週間遅れてお盆休みを取ったので、実家に帰省中です。 今日は瀬戸内芸術祭の会場の一つである豊島に行ってきました。

色々な作品があって、とても興味深かったです。

なんでElixirなん

さて本題ですが、elixirを触った理由は以下の理由があります。

Erlangは以前に勉強していたのですが、OTPやErlang VMの軽量プロセスの恩恵を感じられるような物を作ることができませんでした。 そこで今回はElixirを使ってみて、Erlangとのシンタックスなどの違い、軽量プロセス感の通信を使ったアプリケーションを作ってみてElixir, Erlang VMの良さに触れてみたいと考えました。 これらを考えた結果今回は、特定の深さ以下の任意のディレクトリ以下を走査し、ファイルの中に特定の単語があるか調べるプログラムを組んでみました。 github.com

アクターモデル

Erlang VMではプロセス間のメッセージ通信にアクターモデルの概念を念頭に行います。 僕は説明が上手くないので詳しくはwikipediaの基本概念部分を参照して欲しいです。 今回私が作成物のプロセスは以下の図のようにメッセージ通信をしています。 f:id:yakuta55:20190819184616p:plain

順を追って説明すると、 まずワーカープロセスがスケジューラーに:readyというメッセージを送信します。 :readyを受け取ったスケジューラーは、送り主のワーカープロセスに対し:work, :idle, :shutdownの内どれかのメッセージを送り返します。

:workはスケジューラーの中に実行すべきキューがまだある場合に送信されます。:workを受け取ったワーカープロセスは勝利した結果を:answerというメッセージで返信します。その後ワーカープロセスは、また:readyを送信するようになります。

:idleは実行すべきキューは無いが、プロセスを待たせたいときに使い、:idleを受け取ったワーカープロセスはn msほどスリープし:readyを再び送信します。

:shutdownは実行すべきキューが空かつ待ちが必要のないときに送信され、受け取ったワーカープロセスはexitします。

schedulerプロセスとワーカープロセスが相互に通信をしあい、溜まったキューの処理を進めていきます。 また複数のワーカープロセスを使うことでErlang VMがコンピューターリソースを効率的に使用します。

まとめ

オブジェクト指向は状態を元に振る舞いが変化するならば、アクターモデルは受け取った状態を元に振る舞いを変えるというような感じでしょうか。 どことなく似ているようで全然違うのですが、新しい概念に触れられて勉強になりました。 またErlang VMがワーカープロセスの数を増やせば増やすほどコンピュータリソースを食っていき、カツカツになっていく様を見るのは楽しかったです。 次はGenServerなんかに触れられたらなぁと思います。

参考

プロセス間のメッセージやり取りはプログラミングElixirを参考にさせていただきました。 ありがとうございます。