Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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 さんからのご指摘。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした