すごいE本 第14章 on Elixir (GenServer)

# 環境

```shell-session:sh
$ lsb_release -d
Description:	Ubuntu 18.04.2 LTS

$ elixir -v
Erlang/OTP 21 [erts-10.3.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Elixir 1.8.1 (compiled with Erlang/OTP 20)
```

# 14.2 基本的なサーバ

## 子猫サーバの紹介

Mix でプロジェクトを作ってみる。

```shell-session:sh
$ mix new kitty
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/kitty.ex
* creating test
* creating test/test_helper.exs
* creating test/kitty_test.exs

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

    cd kitty
    mix test

Run "mix help" for more commands.

$ cd kitty
$ tree
.
├── README.md
├── config
│   └── config.exs
├── lib
│   └── kitty.ex
├── mix.exs
└── test
    ├── kitty_test.exs
    └── test_helper.exs
```

猫を示すデータは構造体で。

```elixir:kitty/lib/kitty.ex
defmodule Kitty do
  alias __MODULE__, as: Me

  defstruct [:name, :color, :description]

  defp make_cat(name, color, desc) do
    %Me{name: name, color: color, description: desc}
  end

  ###
  # Client API

  def start_link, do: spawn_link(&init/0)

  # 同期呼び出し
  def order_cat(pid, name, color, desc) do
    ref = Process.monitor(pid)
    send(pid, {self(), ref, {:order, name, color, desc}})

    receive do
      {^ref, cat} ->
        Process.demonitor(ref, [:flush])
        cat

      {:DOWN, ^ref, :process, _pid, reason} ->
        raise inspect(reason)
    after
      5_000 -> raise "timeout"
    end
  end

  # 非同期呼び出し
  def return_cat(pid, cat = %Me{}) do
    send(pid, {:return, cat})
    :ok
  end

  # 同期呼び出し
  def close_shop(pid) do
    ref = Process.monitor(pid)
    send(pid, {self(), ref, :terminate})

    receive do
      {^ref, :ok} ->
        Process.demonitor(ref, [:flush])
        :ok

      {:DOWN, ^ref, :process, _pid, reason} ->
        raise inspect(reason)
    after
      5_000 -> raise "timeout"
    end
  end

  ###
  # Server functions

  defp init, do: loop([])

  defp loop(cats) do
    receive do
      {pid, ref, {:order, name, color, desc}} ->
        case cats do
          [] ->
            send(pid, {ref, make_cat(name, color, desc)})
            loop(cats)

          [cat | cats] ->
            send(pid, {ref, cat})
            loop(cats)
        end

      {:return, cat = %Me{}} ->
        loop([cat | cats])

      {pid, ref, :terminate} ->
        send(pid, {ref, :ok})
        terminate(cats)

      unknown ->
        IO.puts("Unknown message: #{inspect(unknown)}")
        loop(cats)
    end
  end

  defp terminate(cats) do
    cats
    |> Enum.each(fn cat -> IO.puts("#{cat.name} was set free.") end)

    :ok
  end
end
```
```shell-session:sh
$ mix format
$ iex -S mix
```
```elixir:iex
Erlang/OTP 21 [erts-10.3.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Compiling 1 file (.ex)
Generated kitty app
Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> pid = Kitty.start_link()
#PID<0.135.0>
iex(2)> cat1 = Kitty.order_cat(pid, "Carl", :brown, "loves to burn bridges")
%Kitty.Cat{color: :brown, description: "loves to burn bridges", name: "Carl"}
iex(3)> Kitty.return_cat(pid, cat1)
:ok
iex(4)> Kitty.order_cat(pid, "Jimmy", :orange, "cuddly")
%Kitty.Cat{color: :brown, description: "loves to burn bridges", name: "Carl"}
iex(5)> Kitty.order_cat(pid, "Jimmy", :orange, "cuddly")
%Kitty.Cat{color: :orange, description: "cuddly", name: "Jimmy"}
iex(6)> Kitty.return_cat(pid, cat1)
:ok
iex(7)> Kitty.close_shop(pid)
Carl was set free.
:ok
iex(8)> Kitty.close_shop(pid)
** (RuntimeError) :noproc
    (kitty) lib/kitty.ex:51: Kitty.close_shop/1
```

## 開始関数

`Kitty` モジュールを参考に、サーバ汎用化モジュールを作っている。
OTP `GenServer` のプロトタイプとして見ることができる。

```elixir:kitty/lib/myserver.ex
defmodule MyServer do
  # このモジュールは、サーバ実装者が使う。

  def start(mod, init_state) do
    spawn(fn -> init(mod, init_state) end)
  end

  def start_link(mod, init_state) do
    spawn_link(fn -> init(mod, init_state) end)
  end

  # 同期呼び出し
  def call(pid, msg) do
    ref = Process.monitor(pid)
    send(pid, {:sync, self(), ref, msg})

    receive do
      {^ref, reply} ->
        Process.demonitor(ref, [:flush])
        reply

      {:DOWN, ^ref, :process, _pid, reason} ->
        raise inspect(reason)
    after
      5_000 -> raise "timeout"
    end
  end

  # 非同期呼び出し
  def cast(pid, msg) do
    send(pid, {:async, msg})
    :ok
  end

  # handle_call/3 を実装した側は、
  # 同期呼び出しなのでクライアントへ値を返さなければならない。
  # これは、実装側にクライアントの参照を意識させないための配慮。
  def reply({pid, ref}, reply), do: send(pid, {ref, reply})

  defp init(mod, init_state), do: loop(mod, mod.init(init_state))

  defp loop(mod, state) do
    receive do
      {:async, msg} ->
        loop(mod, mod.handle_cast(msg, state))

      {:sync, pid, ref, msg} ->
        loop(mod, mod.handle_call(msg, {pid, ref}, state))
    end
  end
end
```

## 子猫サーバの汎用化

```elixir:kitty/lib/kitty2.ex
defmodule Kitty2 do
  alias __MODULE__, as: Me

  defstruct [:name, :color, :description]

  defp make_cat(name, color, desc) do
    %Me{name: name, color: color, description: desc}
  end

  ###
  # Client API

  # MyServer を経由して、
  # このモジュールの Server functions 以下が呼び出される。

  def start_link, do: MyServer.start_link(Me, [])

  def order_cat(pid, name, color, desc) do
    MyServer.call(pid, {:order, name, color, desc})
  end

  def return_cat(pid, cat = %Me{}) do
    MyServer.cast(pid, {:return, cat})
  end

  def close_shop(pid) do
    MyServer.call(pid, :terminate)
  end

  ###
  # Server functions

  # 以下は MyServer に対するコールバック関数。
  # MyServer が Kitty2 を操作できるようにハンドルを用意してるわけだね。

  # サーバの初期状態が空リストであることを前提にしている。
  def init([]), do: []

  # MyServer.reply/2 のお陰で、from の詳細を知らなくも済んでいる。
  def handle_call({:order, name, color, desc}, from, cats) do
    case cats do
      [] ->
        MyServer.reply(from, make_cat(name, color, desc))
        cats

      [cat | cats] ->
        MyServer.reply(from, cat)
        cats
    end
  end

  def handle_call(:terminate, from, cats) do
    MyServer.reply(from, :ok)
    terminate(cats)
  end

  def handle_cast({:return, cat = %Me{}}, cats), do: [cat | cats]

  defp terminate(cats) do
    cats
    |> Enum.each(fn cat -> IO.puts("#{cat.name} was set free.") end)

    exit(:normal)
  end
end
```

`Kitty2` は `Kitty` と等価に動作する。

# 14.4 コールバック・トゥ・ザ・フューチャー

OTP `GenServer` のコールバック関数の紹介。
ざっと読んでコンセプトを理解する。

後は hexdocs を眺める。
[GenServer — Elixir vx.x.x](https://hexdocs.pm/elixir/GenServer.html#content)

# 14.5 スコッティ、転送を頼む

先の `Kitty` プロジェクトをルートとする。

```shell-session:sh
$ mkdir lib/kitty
$ touch lib/kitty/genserver.ex
```
```elixir:kitty/lib/kitty/genserver.ex
defmodule Kitty.GenServer do
  use GenServer
end
```

`use GenServer` の意味ってなんだろ？

Erlang だと `-behavior(gen_server)` と宣言する。
そして `gen_server` であるための必須条件としてコールバックを実装する。
このコールバックの実装が、振る舞い（ビヘイビア）の操作的意味を与える。
したがって、コールバックの実装が不足していると Erlang はエラーを吐く。
Java のインターフェースに近いのかな？

```shell-session:sh
$ mix compile
Compiling 3 files (.ex)
warning: function init/1 required by behaviour GenServer is not implemented (in module Kitty.GenServer).

We will inject a default implementation for now:

    def init(init_arg) do
      {:ok, init_arg}
    end

You can copy the implementation above or define your own that converts the arguments given to GenServer.start_link/3 to the server state.

  lib/kitty/genserver.ex:1

Generated kitty app
```

コンパイルは成功したが、`init/0` が無いよと警告が出た。
説明通りに実装してみる。

```elixir:kitty/lib/kitty/genserver.ex
defmodule Kitty.GenServer do
  use GenServer

  @impl true
  def init(init_arg), do: {:ok, init_arg}
end
```

`@impl true` はコンパイラに対するアノテーション。
「何かしらのビヘイビアのコールバックです」と告げている。
「GenServer のコールバックです」と告げる場合は `@impl GenServer` と書く。
書かなくてもコンパイル通るけど、書いておいた方がドキュメント性が増すね。

```shell-session:sh
$ mix compile
Compiling 1 file (.ex)
```

Elixir の場合、`init/1` のみを実装すればいいんだね。楽だ。
ということは、これって Erlang と少し意味が違うね。
`-behavior(gen_server)` はインターフェースの取り込みで、
`use GenServer` は抽象クラスの取り込みに近いかな？
あれ？抽象クラスは多重継承できないんだったっけ？じゃ、違うか。
なんにせよ、Elixir の方は意味的には「継承」になってるんだね。

```elixir:kitty/lib/kitty/genserver.ex
defmodule Kitty.GenServer do
  use GenServer
  alias __MODULE__, as: Me

  defstruct [:name, :color, :description]

  defp make_cat(name, color, desc) do
    %Me{name: name, color: color, description: desc}
  end

  ###
  # Client API

  def start_link do
    # start_link(module, init_arg, options \\ [])
    GenServer.start_link(Me, [])
  end

  # 同期呼び出し
  def order_cat(pid, name, color, desc) do
    GenServer.call(pid, {:order, name, color, desc})
  end

  # 非同期呼び出し
  def return_cat(pid, cat = %Me{}) do
    GenServer.cast(pid, {:return, cat})
  end

  # 同期呼び出し
  def close_shop(pid) do
    GenServer.call(pid, :terminate)
  end

  ###
  # Callbacks

  @impl true
  def init([]), do: {:ok, []}

  @impl true
  def handle_call({:order, name, color, desc}, _from, cats) do
    case cats do
      [] -> {:reply, make_cat(name, color, desc), cats}
      [cat | cats] -> {:reply, cat, cats}
    end
  end

  @impl true
  def handle_call(:terminate, _from, cats) do
    {:stop, :normal, :ok, cats}
  end

  @impl true
  def handle_cast({:return, cat = %Me{}}, cats) do
    {:noreply, [cat | cats]}
  end

  @impl true
  def handle_info(msg, cats) do
    IO.puts("Unexpected message: #{inspect(msg)}")
    {:noreply, cats}
  end

  @impl true
  def terminate(:normal, cats) do
    cats
    |> Enum.each(fn cat -> IO.puts("#{cat.name} was set free.") end)

    :ok
  end

  # code_change/3 は定義しない。
end
```
```shell-session:sh
$ mix format
$ iex -S mix
```
```elixir:iex  
iex(1)> {:ok, pid} = Kitty.GenServer.start_link()
{:ok, #PID<0.144.0>}
iex(2)> send(pid, "Test handle_info")
Unexpected message: "Test handle_info"
"Test handle_info"
iex(3)> cat = Kitty.GenServer.order_cat(pid, "Cat Stevens", :white, "not actually a cat")
%Kitty.GenServer{
  color: :white,
  description: "not actually a cat",
  name: "Cat Stevens"
} 
iex(4)> Kitty.GenServer.return_cat(pid, cat)
:ok
iex(5)> Kitty.GenServer.order_cat(pid, "Kitten Mittens", :black, "look at them little paws!")
%Kitty.GenServer{
  color: :white,
  description: "not actually a cat",
  name: "Cat Stevens"
} 
iex(6)> Kitty.GenServer.return_cat(pid, cat)
:ok
iex(7)> Kitty.GenServer.close_shop(pid)
Cat Stevens was set free.
:ok
iex(8)> Kitty.GenServer.close_shop(pid)
** (exit) exited in: GenServer.call(#PID<0.144.0>, :terminate, 5000)
    ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
    (elixir) lib/gen_server.ex:989: GenServer.call/3
```

うむ。