Wait a minute
プログラミングErlangのOTP入門をElixirで行っていく内容です。
前回の記事では、プロセスを使って設計的な部分を学んだが、
今回はGenServerを利用したり、本格的(?)にOTPを使っていきます。
前回の記事では、プロセスを使って設計的な部分を学んだが、
今回はGenServerを利用したり、本格的(?)にOTPを使っていきます。
ElixirからOTPってどうやって使うんだ?とか、そもそもOTPって何?って思う人のための記事…(主に私です!!)
Goal
- プログラミングErlangのOTP入門をElixirで実施し、ブログへアップする(時間制限なし)
- ElixirからOTPを使うというのは、どのようにすればいいのかサンプルとなるものを作成する
- とにかく色々作る(だいぶファジー)
Outcome
2016/03/02 23:20 GenServerを使うサンプルを作成した。
Erlang側のソースコードで知らない文法があった。
Erlang側のソースコードで知らない文法があった。
Analyze
Erlang側の文法がまだ網羅できていない。
Erlangの知らない文法はマクロであった。
Erlangの知らない文法はマクロであった。
Learn
OTP(GenServer)の使い方が分かった。
Content
Dev-Environment
- OS: Windows8.1
- Erlang: Eshell V7.2.1, OTP-Version 18.1
- Elixir: v1.2.0
Index
Using OTP with Elixir (GenServer ed)
|> Prepare
|> Using GenServer
|> Prepare
|> Using GenServer
Prepare
プロジェクトの準備を行います。
> cd プロジェクト作成ディレクトリ
> mix new elixir_otp
...
> cd elixir_otp
> mix test
...
本記事内においてプロジェクトと言った場合、”elixir_otp”を指す。
GenServerでコールバックモジュールを書くときの簡単な3点計画と言うものがあるみたいです。
GenServerでコールバックモジュールを書くときの簡単な3点計画と言うものがあるみたいです。
- コールバックモジュールの名前を決める
- インターフェイス関数を書く
- コールバックモジュールの6つの必須コールバック関数を書く
覚えておけば、どっかで役に立つはず~
First Step
1. コールバックモジュール名
モジュール名は”MyBank”
簡単な支払いシステムを作るとのこと。
簡単な支払いシステムを作るとのこと。
2. インターフェイスルーチンを書く
- start()
銀行をオープンする。
- stop()
銀行をクローズする。
- new_account(who)
新しい口座を作る。
- deposit(who, amount)
銀行にお金を預ける。
- withdraw(who, amount)
残高が十分であればお金を引き出す。
上記のインターフェイスルーチンを実装します。
File: lib/my_bank.ex
defmodule MyBank do
use GenServer
## Interface routine
def start do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def stop do
GenServer.call(__MODULE__, :stop)
end
def new_account(who) do
GenServer.call(__MODULE__, {:new, who})
end
def deposit(who, amount) do
GenServer.call(__MODULE__, {:add, who, amount})
end
def withdraw(who, amount) do
GenServer.call(__MODULE__, {:remove, who, amount})
end
end
GenServer.call/2は、サーバに対して遠隔手続きの呼び出しをするために使われます。
3. コールバックルーチンを書く
6つのコールバックルーチンを実装します。
- init/1
- handle_call/3
- handle_cast/2
- handle_info/2
- terminate/2
- code_change/3
GenServer用のテンプレート。(自分用です)
Example
defmodule GenServerMiniTemplate do
use GenServer
def start_link(server) do
GenServer.start_link(__MODULE__, [server], name: __MODULE__)
end
def init([]) do
{:ok, state}
end
def handle_call(_request, _from, state) do
{:reply, reply, state}
end
def handle_cast(_msg, state) do
{:noreply, state}
end
def handle_info(_info, state) do
{:noreply, state}
end
def terminate(_reason, _state) do
:ok
end
def code_change(_old_vsn, state, extra) do
{:ok, state}
end
end
インターフェイスルーチンの引数がテンプレートの引数と一致するようにします。
今回重要になるのは、handle_call/3のコールバック…
インターフェイスルーチンにある3つの問合せに対応する必要がある。
今回重要になるのは、handle_call/3のコールバック…
インターフェイスルーチンにある3つの問合せに対応する必要がある。
File: lib/my_bank.ex
defmodule MyBank do
use GenServer
def start do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def stop do
GenServer.call(__MODULE__, :stop)
end
def new_account(who) do
GenServer.call(__MODULE__, {:new, who})
end
def deposit(who, amount) do
GenServer.call(__MODULE__, {:add, who, amount})
end
def withdraw(who, amount) do
GenServer.call(__MODULE__, {:remove, who, amount})
end
def init([]) do
{:ok, :ets.new(__MODULE__, [])}
end
def handle_call({:new, who}, _from, tab) do
reply = case :ets.lookup(tab, who) do
[] ->
:ets.insert(tab, {who, 0})
{:welcome, who}
[_] ->
{who, :you_already_are_a_customer}
end
{:reply, reply, tab}
end
def handle_call({:add, who, x}, _from, tab) do
reply = case :ets.lookup(tab, who) do
[] -> :not_a_customer
[{who, balance}] ->
new_balance = balance + x
:ets.insert(tab, {who, new_balance})
{:thanks, who, :your_balance_is, new_balance}
end
{:reply, reply, tab}
end
def handle_call({:remove, who, x}, _from, tab) do
reply = case :ets.lookup(tab, who) do
[] -> :not_a_customer
[{who, balance}] when x <= balance ->
new_balance = balance - x
:ets.insert(tab, {who, new_balance})
{:thanks, who, :your_balance_is, new_balance}
[{who, balance}] ->
{:sorry, who, :you_only_have, balance, :in_the_bank}
end
{:reply, reply, tab}
end
def handle_call(:stop, _from, tab) do
{:stop, :normal, :stopped, tab}
end
def handle_cast(_msg, state) do
{:noreply, state}
end
def handle_info(_info, state) do
{:noreply, state}
end
def terminate(_reason, _state) do
:ok
end
def code_change(_old_vsn, state, _extra) do
{:ok, state}
end
end
Note:
init/1は、{:ok, state}を返す必要がある。
このときのstateの値は、handle_callの第三引数に使われる。
handle_call(:stop, _from, tab)は、サーバの停止を意味している
{:stop, :normal, :stopped, tab}を返す。
このときの:stoppedはstop/0の戻り値になる。
何はともあれ、実行してみましょう。
Example:
## 一通り使ってみる
iex> MyBank.start
{:ok, #PID<0.100.0>}
iex> MyBank.deposit("darui", 10)
:not_a_customer
iex> MyBank.new_account("darui")
{:welcome, "darui"}
iex> MyBank.deposit("darui", 10)
{:thanks, "darui", :your_balance_is, 10}
iex> MyBank.deposit("darui", 30)
{:thanks, "darui", :your_balance_is, 40}
iex> MyBank.withdraw("darui", 15)
{:thanks, "darui", :your_balance_is, 25}
iex> MyBank.withdraw("darui", 45)
{:sorry, "darui", :you_only_have, 25, :in_the_bank}
## もう一人アカウントを作ってみる
iex> MyBank.deposit("hoge", 1000)
:not_a_customer
iex> MyBank.new_account("hogehoge")
{:welcome, "hogehoge"}
iex> MyBank.deposit("hogehoge", 1000)
{:thanks, "hogehoge", :your_balance_is, 1000}
iex> MyBank.deposit("darui", 10)
{:thanks, "darui", :your_balance_is, 35}
## サーバを止めてみる
iex> MyBank.stop
:stopped
iex> MyBank.deposit("darui", 10)
** (exit) exited in: GenServer.call(MyBank, {:add, "darui", 10}, 5000)
** (EXIT) no process
(elixir) lib/gen_server.ex:564: GenServer.call/3
iex> MyBank.start
{:ok, #PID<0.115.0>}
iex> MyBank.deposit("darui", 10)
:not_a_customer
GenServerのインターフェイス関数を全てやったわけではないですが、
これが基本的な考え方とのことです。
今回のは簡単なサンプルですが、大概はこれで十分対応できます。
これが基本的な考え方とのことです。
今回のは簡単なサンプルですが、大概はこれで十分対応できます。
後は、少し複雑になりますが、GenServerにはnoreplyで応答させ、
本当の応答は別のプロセスに委任する手法が多いみたいです。
それに関しては、Erlangの以下のドキュメントが参考になるそうです。
まぁ、それは追々やりましょう。
本当の応答は別のプロセスに委任する手法が多いみたいです。
それに関しては、Erlangの以下のドキュメントが参考になるそうです。
まぁ、それは追々やりましょう。
参考1: Erlang - OTP Design Principles User’s Guide
参考2: Erlang - 6 sys and proc_lib
参考3: Erlang - sys
参考4: Erlang - proc_lib
参考2: Erlang - 6 sys and proc_lib
参考3: Erlang - sys
参考4: Erlang - proc_lib
Improve
Erlang側の文法をもう少し網羅する必要がある。
ブログのテンプレートが分かり辛い。これならば前の方が良かった。
同様に学習メソッドにも改善が必要である。
同様に学習メソッドにも改善が必要である。
Prepare
なし
Speaking to oneself
人に読まれることを考えると、今のブログテンプレートは良くないので改善します。
全体的に質が下がった気がする…やっちまったな(汗)
全体的に質が下がった気がする…やっちまったな(汗)