LoginSignup
4
1

More than 5 years have passed since last update.

はじめてな Elixir(20) GenServer で定期的なお仕事をする(いい加減つぎに行こうぜ編)

Last updated at Posted at 2018-11-07

GenServer で定期的に何かする動作を(今日こそ)実装してみます。

はじめてなElixir(19) GenServer で定期的なお仕事をする(まだ終わってない編) で、中途半端な状態で終わってしまい、放置していました。今日はこれを終わらせます。

改めて、GenServer で定期的に何かする動作を GenServerで実装してる風に見えないようにして 実装してみます。

Interface をきちんと使う

改めて Elixirの 16.5節を御覧ください。リファレンスの Client/Server APIs も。

@behavior@impl を使って、さらに GenServer をラップする

前回は @impl@behavior に感心して、肝心の GenServer を見せなくするラッパ関数を書くのを完全に忘れてました。

temp16.ex
defmodule Temp16 do
  @behaviour GenServer

  def start_link(pname) do
    GenServer.start_link(__MODULE__, nil, name: pname)
  end

  def next(pname) do
    GenServer.cast(pname, :next)
  end

  def last(pname) do
    GenServer.call(pname, :last)
  end

  @impl GenServer
  def init(_void) do
    {:ok, []}
  end

  @impl GenServer
  def handle_cast(:next, []) do
    {:noreply, [0]}
  end

  @impl GenServer
  def handle_cast(:next, [head | tail]) do
    {:noreply, [head + 1 | [head | tail]]}
  end

  @impl GenServer
  def handle_call(:last, _from, []) do
    {:reply, {:error, []}, []}
  end

  @impl GenServer
  def handle_call(:last, _from, [head | tail]) do
    {:reply, {:ok, head}, [head | tail]}
  end

  def test() do
    Temp16.start_link(:mypid)
    IO.inspect(Temp16.last(:mypid)) # should get :error
    IO.inspect(Temp16.next(:mypid))
    IO.inspect(Temp16.last(:mypid))
    IO.inspect(Temp16.next(:mypid))
    IO.inspect(Temp16.last(:mypid))
    IO.inspect(Temp16.next(:mypid))
    IO.inspect(Temp16.last(:mypid))
    IO.inspect(Temp16.next(:mypid))
    IO.inspect(Temp16.last(:mypid))
    :ok
  end

test() 関数を見て下さい。GenServer が見えなくなってます。Temp16 って名前に色気がないのでありがたみがないですけど、とにかく GenServer は見えなくなりました。

iex.ex
iex(1)> Temp16.test()
{:error, []}
:ok
{:ok, 0}
:ok
{:ok, 1}
:ok
{:ok, 2}
:ok
{:ok, 3}
:ok

とまあ、たったこれだけのことを忘れて、追加するのに大変間が空いてしまいました。

定期的に自律的な動作を行わせるのに GenServer のラップもする

調子よくラッパ関数を書けたので、定期的にプロセスが刺激を受けてチョコチョコ動作するモジュールにもラッパ関数を入れてみます。

temp17.ex
defmodule Temp17 do
  @behaviour GenServer

  def start_link(pname) do
    GenServer.start_link(__MODULE__, nil, name: pname)
  end

  def last(pname) do
    GenServer.call(pname, :last)
  end

  def list(pname) do
    GenServer.call(pname, :list)
  end


  @impl GenServer
  def init(_void) do
    set_interval()
    {:ok, []}
  end

  defp set_interval() do
    Process.send_after(self(), :wakeup, 2 * 1000) # in 2 seconds
  end

  @impl GenServer
  def handle_info(:wakeup, temp_list) do
    new_temp_list = append_new(temp_list)
    set_interval()
    {:noreply, new_temp_list}
  end

  defp append_new([]) do
    [0]
  end

  defp append_new([head | tail]) do
    [head + 1 | [head | tail]]
  end

  @impl GenServer
  def handle_cast(:next, []) do
    {:noreply, [0]}
  end

  @impl GenServer
  def handle_cast(:next, [head | tail]) do
    {:noreply, [head + 1 | [head | tail]]}
  end

  @impl GenServer
  def handle_call(:last, _from, []) do
    {:reply, {:error, []}, []}
  end

  @impl GenServer
  def handle_call(:last, _from, [head | tail]) do
    {:reply, {:ok, head}, [head | tail]}
  end

  @impl GenServer
  def handle_call(:list, _from, list) do
    {:reply, {:ok, list}, list}
  end

いろいろ仕込んでるので長くなってしまってますけど、使っているのは一部です。これを実行するのにも、もう GenServer を書かなくて良くなってます。

iex.ex
iex(2)> Temp17.start_link(:mypid)
{:ok, #PID<0.110.0>}
iex(3)> Temp17.last(:mypid) # start_link 直後に実行
{:error, []}
iex(4)> Temp17.last(:mypid) # わずかに待って実行
{:ok, 0}
iex(5)> Temp17.last(:mypid)
{:ok, 3}
iex(6)> Temp17.list(:mypid) # 暫く経つとリストが延びてる
{:ok, [6, 5, 4, 3, 2, 1, 0]}

せっかく名前が付けられるので並行して、もう一つ動かしてみます。

iex.ex
iex(7)> Temp17.start_link(:hello) # 別のプロセス名でも一つ起動
{:ok, #PID<0.116.0>}
iex(8)> Temp17.last(:hello)      # 動いてるか確認
{:ok, 2}
iex(9)> Temp17.last(:mypid)      # 最初に動かしたのはどうなってるか
{:ok, 264}
iex(10)> Temp17.list(:mypid)     # 放置してたのでリストが長くなってる…
{:ok,
 [266, 265, 264, 263, 262, 261, 260, 259, 258, 257, 256, 255, 254, 253, 252,
  251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240, 239, 238, 237,
  236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224, 223, 222,
  221, 220, 219, ...]}
iex(11)> Temp17.last(:hello)      # 新しい方も順調
{:ok, 13}
iex(12)> Temp17.list(:hello)      # 新しい方をリストで確認
{:ok, [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]}

せっかくなので、さらにもう一つ動かしてみましょう。

iex.ex
iex(13)> Temp17.start_link(:world) # もう一つ動かしてみます
{:ok, #PID<0.123.0>}
iex(14)> Temp17.last(:hello)       # 2番目のをチェック
{:ok, 101}
iex(15)> Temp17.last(:mypid)       # 最初のをチェック
{:ok, 362}
iex(16)> Temp17.last(:world)       # 最新のプロセスも動いてます
{:ok, 4}

かなりやりたいことに近づいてきました。ふぅ。

参考文献

Elixir リファレンス GenServer
Elixir リファレンス GenServer の Receiving Regular Messages

変更履歴

2019.02.04 temp16.ex のコピペミスで init 関数が落ちてたのを直しました。 @CostlierRain464 さんからのご指摘。

4
1
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
4
1