GenServerのhandle_info/2とProcess.send_after/4の組み合わせによる定期実行

自分は、Elixirで一定間隔で繰り返し実行される処理を`:timer.sleep/1`と再帰処理の組み合わせで実施していた。

しかし、GerServerを利用する分には、`Process.send_after/4`と`GenServer.handle_info/2`を利用すれば、シンプルに実装できることを先日知った。

具体的な対応方法としては、`GenServer.init/1`中で`Process.send_after/4`を実行。指定の時間後に`GenServer.handle_info/2`が呼び出されるので、その中で再び`Process.send_after/4`を呼び出せばよい。

元ネタは[こちら](https://hexdocs.pm/elixir/GenServer.html#module-receiving-regular-messages)。

また、`Process.send_after/4`の詳細は[こちら](https://hexdocs.pm/elixir/Process.html#send_after/4)を参照。

## 実装

一定間隔でFizzBuzzの結果を返すGenServerのコードを書いてみる。

まず、プロジェクトの作成。
「Supervisor」ありでプロジェクト作成をしているが、別に無くても良い。

```
$mix new fizzbuzz --sup
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/fizzbuzz.ex
* creating lib/fizzbuzz/application.ex
* creating test
* creating test/test_helper.exs
* creating test/fizzbuzz_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd fizzbuzz
    mix test

Run "mix help" for more commands.
```

つぎに、定期実行されるGernServerのコードを用意する。
内容としては、一定間隔でFizzBuzzの値がコンソールに出力されるようにした。
GenServerを利用しているので、FizzBuzzの結果は"状態"の値を利用して算出する実装とした。そのため、FizzBuzz実行後の"状態"はインクリメントしていく。

```elixir:./lib/fizzbuzz/fizzbuzz_auto.ex
defmodule Fizzbuzz.Auto do
  use GenServer

  def calc_fb({n, 0, 0}), do: {n, "FizzBuzz"}
  def calc_fb({n, 0, _}), do: {n, "Fizz"}
  def calc_fb({n, _, 0}), do: {n, "Buzz"}
  def calc_fb({n, _, _}), do: {n, to_string(n)}

  def fizzbuzz(n) do
    cond do
      n < 1 -> {n , "Cannot calculate"}
      true  -> calc_fb({n, rem(n,3), rem(n, 5)})
    end
  end

  def schedule_work do
    # In 5 Seconds
    Process.send_after(self(), :work, 5 * 1000)
  end

  # 以降、GenServerの実装

  def init(state) do
    IO.puts("Fizzbuzz.Auto: init/1  call")
    schedule_work()
    {:ok, state}
  end

  def handle_info(:work, current_num) do
    {n, v} = current_num |> fizzbuzz()
    IO.puts("Fizzbuzz.Auto: schedule_work : #{n} : #{v}")

    # Reschedule once more
    schedule_work()

    {:noreply, current_num + 1}
  end
end
```

`init/1`から、`Process.send_after/4`を実行する関数`schedule_work/0`を呼び出す。この時、`Process.send_after/4`では、引数に「自分自身のPID」「メッセージ（今回は`:work`）」「待ち時間（今回は`5000ms`）」を渡してる。

5秒（5000ms）後に、`handle_info(:work, current_num)`が呼び出され、この中でFizzBuzz処理の実行後にふたたび関数`schedule_work/0`が呼び出される。


## 実行結果

実際に動かしてみる。
`iex -S mix`でiexを起動後、`GenServer.start(Fizzbuzz.Auto, 1)`でFizzbuzz.Autoを実行する。ここで、「`1`」は初期の"状態"である。

```
$iex -S mix
Erlang/OTP 22 [erts-10.4.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Compiling 3 files (.ex)
Generated fizzbuzz app
Interactive Elixir (1.9.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> GenServer.start(Fizzbuzz.Auto, 1)
Fizzbuzz.Auto: init/1  call
{:ok, #PID<0.168.0>}
Fizzbuzz.Auto: schedule_work : 1 : 1
Fizzbuzz.Auto: schedule_work : 2 : 2
Fizzbuzz.Auto: schedule_work : 3 : Fizz
Fizzbuzz.Auto: schedule_work : 4 : 4
Fizzbuzz.Auto: schedule_work : 5 : Buzz
Fizzbuzz.Auto: schedule_work : 6 : Fizz
Fizzbuzz.Auto: schedule_work : 7 : 7
Fizzbuzz.Auto: schedule_work : 8 : 8
Fizzbuzz.Auto: schedule_work : 9 : Fizz
Fizzbuzz.Auto: schedule_work : 10 : Buzz
Fizzbuzz.Auto: schedule_work : 11 : 11
Fizzbuzz.Auto: schedule_work : 12 : Fizz
Fizzbuzz.Auto: schedule_work : 13 : 13
Fizzbuzz.Auto: schedule_work : 14 : 14
Fizzbuzz.Auto: schedule_work : 15 : FizzBuzz
Fizzbuzz.Auto: schedule_work : 16 : 16
〜（以下、続いていく）〜
```

実行すると、5秒ごとにメッセージが出力される。

## 課題

`Process.send_after/4`の読み出しを`init/1`の時点から開始しないと、うまく動いてくれない。
例えば、`init/1`で`schedule_work/0`を呼び出ないでGenServerとして起動し、その後に`schedule_work/0`を実行しても、iex上では何も起こっていないように見える。

```
$cat ./lib/fizzbuzz/fizzbuzz_auto.ex

〜（略）〜
  def init(state) do
    IO.puts("Fizzbuzz.Auto: init/1  call")
#    schedule_work()
    {:ok, state}
  end
〜（略）〜

$iex -S mix

iex(1)> GenServer.start(Fizzbuzz.Auto, 1)
Fizzbuzz.Auto: init/1  call
{:ok, #PID<0.146.0>}
iex(2)> Fizzbuzz.Auto.schedule_work()
#Reference<0.2803571744.3830972420.104800>
iex(3)>
```
この辺りの理屈はまだ理解しきれていないので、わかったら追記していきたい。

## 参考情報

 - https://hexdocs.pm/elixir/GenServer.html#module-receiving-regular-messages
 - https://hexdocs.pm/elixir/Process.html#send_after/4
 - https://dockyard.com/blog/2018/12/12/phoenix-liveview-interactive-real-time-apps-no-need-to-write-javascript