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

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

[はじめてなElixir(19) GenServer で定期的なお仕事をする（まだ終わってない編）](https://qiita.com/kikuyuta/items/245513914eb68bd338b5) で、中途半端な状態で終わってしまい、放置していました。今日はこれを終わらせます。

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

# Interface をきちんと使う
改めて Elixirの 16.5節を御覧ください。リファレンスの [Client/Server APIs](https://hexdocs.pm/elixir/GenServer.html#module-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](https://hexdocs.pm/elixir/GenServer.html#content)
[Elixir リファレンス GenServer の Receiving Regular Messages](https://hexdocs.pm/elixir/GenServer.html#module-receiving-regular-messages)

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