すごいE本をElixirでやる(44)
第14章 OTPの紹介 - 14.2 基本的なサーバ - 開始関数 から
第14章OTPの紹介
14.2 基本的なサーバ
開始関数
開始関数(start、 start_link、init)の実装
defmodule MyServer do
### Public API
def start(module, initial_state), do: spawn(fn -> init(module, initial_state) end)
def start_link(module, initial_state), do: spawn_link(fn -> init(module, initial_state) 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(reason)
after 5000 ->
raise(:timeout)
end
end
def cast(pid, msg) do
send(pid, {:async, msg})
:ok
end
def reply({pid, ref}, reply) do
send(pid, {ref, reply})
end
### Private stuff
defp init(module, initial_state), do: loop(module, module.init(initial_state))
defp loop(module, state) do
receive do
{:async, msg} ->
loop(module, module.handle_cast(msg, state))
{:sync, pid, ref, msg} ->
loop(module, module.handle_call(msg, {pid, ref}, state))
end
end
end
子猫サーバの汎用化
子猫サーバの再実装です。kitty_server2 は、my_server に定義したインターフェースに対応するコールバックモジュールとして実装します。
defmodule KittyServer do
defmodule Cat, do: defstruct name: "", color: :green, description: ""
### Client API
def start_link, do: MyServer.start_link(__MODULE__, [])
## Synchronous call
def order_cat(pid, name, color, description), do: MyServer.call(pid, {:order, name, color, description})
## This call is asynchronous
def return_cat(pid, cat = %Cat{}), do: MyServer.cast(pid, {:return, cat})
## Synchronous call
def close_shop(pid), do: MyServer.call(pid, :terminate)
### Server functions
def init([]), do: [] ## no treatment of info here!
def handle_call({:order, name, color, description}, from, cats) do
case cats do
[] ->
MyServer.reply(from, make_cat(name, color, description))
cats
[h|t] ->
MyServer.reply(from, h)
t
end
end
def handle_call(:terminate, from, cats) do
MyServer.reply(from, :ok)
terminate(cats)
end
def handle_cast({:return, cat = %Cat{}}, cats) do
[cat|cats]
end
### Private functions
defp make_cat(name, color, description), do: %Cat{name: name, color: color, description: description}
defp terminate(cats) do
for %Cat{name: name} <- cats, do: IO.puts("#{name} was set free.")
exit(:normal)
end
end
14.2 基本的なサーバ で試したのと同じことをしてみる.
Eshell V8.1 (abort with ^G)
Interactive Elixir (1.3.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> import_file("my_server.ex")
{:module, MyServer,
<<70, 79, 82, 49, 0, 0, 15, 168, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 1, 197,
131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:loop, 2}}
iex(2)> import_file("kitty_server2.ex")
{:module, KittyServer,
<<70, 79, 82, 49, 0, 0, 14, 204, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 2, 73,
131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:terminate, 1}}
iex(3)> pid = KittyServer.start_link
#PID<0.96.0>
iex(4)> cat1 = KittyServer.order_cat(pid, :carl, :brown, "loves to burn bridges")
%KittyServer.Cat{color: :brown, description: "loves to burn bridges",
name: :carl}
iex(5)> KittyServer.return_cat(pid, cat1)
:ok
iex(6)> KittyServer.order_cat(pid, :jimmy, :orange, "cuddly")
%KittyServer.Cat{color: :brown, description: "loves to burn bridges",
name: :carl}
iex(7)> KittyServer.order_cat(pid, :jimmy, :orange, "cuddly")
%KittyServer.Cat{color: :orange, description: "cuddly", name: :jimmy}
iex(8)> KittyServer.return_cat(pid, cat1)
:ok
iex(9)> KittyServer.close_shop(pid)
carl was set free.
:ok
iex(10)> KittyServer.close_shop(pid)
** (UndefinedFunctionError) function :noproc.exception/1 is undefined (module :noproc is not available)
:noproc.exception([])
iex:14: MyServer.call/2
コードを共通化/抽象化したが同じ動きをしていることが確認できた.