Registry の via_tuple について

Elixir 1.4 で入った Registry のドキュメントを読んでいると唐突に出てくる via_tuple についてです。Registry とGenServer のドキュメントをちゃんと読んでいる人にとってはどうでもいい記事です。

参考:
  [Registry](https://hexdocs.pm/elixir/master/Registry.html)
  [Registry について](https://qiita.com/mururu/items/b47b8ace0456c422a343)

Registry には大きく分けて2つの用途があります。

1. 動的に立ち上げるプロセスに atom 以外の一意な名前をつける
2. 複数のプロセスに同じ名前をつけて一括でメッセージを送る

Registryの起動時に 1は`:unique`, 2 は`:duplicate`を指定します。
`:unique` な Registry にプロセス(GenServer)を登録する場合、`:name` に via_tuple を指定するのが一般的です。 

## via_tuple

```player.ex
def start_link(name) do
  GenServer.start_link(__MODULE__, name, [name: via_tuple(name)])
end

defp via_tuple(name) do
  {:via, Registry, {PlayerRegistry, name}}
end

def push(name, message) do
  # 呼び出し側は pid を知らなくてもメッセージを送ることができる
  GenServer.cast(via_tuple(name), message) 
end
```

なぜこれが動くかというと GenServer.castの第一引数の型 `server`は以下のようになっていて atom の他に `{:via, module, term}` が使えるからです。

```gen_server.ex
defmodule GenServer do
  @type server :: pid | name | {atom, node}
  @type name :: atom | {:global, term} | {:via, module, term}
  ...
  @spec call(server, term, timeout) :: term
  def call(server, request, timeout \\ 5000) do
   ...
  end
end
```

## via_tupleに渡すmodule

via_tuple の２要素め, `module` に Registry を指定しましたが、実は別のモジュールも渡せます。
[GenServerのドキュメント](https://hexdocs.pm/elixir/GenServer.html#module-name-registration) をみると

```md
    * `{:via, module, term}` - the GenServer is registered with the given
      mechanism and name. The `:via` option expects a module that exports
      `register_name/2`, `unregister_name/1`, `whereis_name/1` and `send/2`.
      One such example is the [`:global` module](http://www.erlang.org/doc/man/global.html) which uses these functions
      for keeping the list of names of processes and their associated PIDs
      that are available globally for a network of Elixir nodes. Elixir also
      ships with a local, decentralized and scalable registry called `Registry`
      for locally storing names that are generated dynamically.
```

:via に渡すモジュールは以下の4つの関数を実装しているモジュールであればなんでもいいようです。

- `register_name/2`
- `unregister_name/1`
- `whereis_name/1`
- `send/2`

ということは Erlang の[global](http://erlang.org/doc/man/global.html) モジュールや [gproc](https://github.com/uwiger/gproc) も使えますし 意味があるかはさておき Process Registry を自作することもできます。



## gen.erl

gen_server.exの該当箇所を読むと、この{:via, module, name} でプロセスをひく機能自体は Erlang の gen.erl で提供されているものであることがわかります。gen_server.erl ではなく gen.erl を使っているのは link するかしないかを引数で指定したいからですね。

https://github.com/elixir-lang/elixir/blob/v1.5.2/lib/elixir/lib/gen_server.ex#L700

```gen_server.ex
  defp do_start(link, module, args, options) do
    case Keyword.pop(options, :name) do
      {nil, opts} ->
        :gen.start(:gen_server, link, module, args, opts)
      {atom, opts} when is_atom(atom) ->
        :gen.start(:gen_server, link, {:local, atom}, module, args, opts)
      {{:global, _term} = tuple, opts} ->
        :gen.start(:gen_server, link, tuple, module, args, opts)
      {{:via, via_module, _term} = tuple, opts} when is_atom(via_module) ->
        :gen.start(:gen_server, link, tuple, module, args, opts)
      {other, _} ->
        raise ArgumentError, """
        expected :name option to be one of:
          * nil
          * atom
          * {:global, term}
          * {:via, module, term}
        Got: #{inspect(other)}
        """
    end
  end
```

参考:
  [Erlang/OTP の gen.erl を読む](https://qiita.com/zenwerk/items/8b23100f05d18b6ad2f4)
  [gen.erl](https://github.com/blackberry/Erlang-OTP/blob/master/lib/stdlib/src/gen.erl)