Goal
- 3日間でElixirを再速習して何かを作る
- 忘れてしまったElixirを力を取り戻すためにElixir Schoolを卒業する
- Elixir Schoolの内容でメモ(備忘録)を作る
Dev-Environment
OS: Windows8.1
Erlang: Eshell V7.2.1, OTP-Version 18.1
Elixir: v1.2.0
Erlang: Eshell V7.2.1, OTP-Version 18.1
Elixir: v1.2.0
Wait a minute
Elixir Shool入校2日目。
Advancedに関してのメモ。
Advancedに関してのメモ。
Index
Elixir Schoolに入校したメモ(2日目)
|> ElixirからErlangを使う
|> エラーハンドリング
|> 実行可能ファイルについて
|> 同時並行性(Process、Agent、Task)
|> OTPの同時並行性(GenServer、GenEvent)
|> OTPのスーパーバイザー
|> ElixirからErlangを使う
|> エラーハンドリング
|> 実行可能ファイルについて
|> 同時並行性(Process、Agent、Task)
|> OTPの同時並行性(GenServer、GenEvent)
|> OTPのスーパーバイザー
ElixirからErlangを使う
Erlangの標準ライブラリを使う
iex> :erlang.size("hello")
5
Note:
Erlangのモジュールは:atom名で記述する。
例) :erlang :os ...etc
使えるものを詳しく知りたいならこれ見ればいいらしい。
参考: [http://erlang.org/doc/apps/stdlib/](http://erlang.org/doc/apps/stdlib/)
Erlangのライブラリを使う
試しに以下のライブラリを”mix deps.get”で取得してみる。
Github: Vagabond/gen_smtp
Github: Vagabond/gen_smtp
defmodule FastRelearning.Mixfile do
...
defp deps do
[{:gen_smtp, "~> 0.9.0"}]
end
end
> mix deps.get
Running dependency resolution
Dependency resolution completed
gen_smtp: 0.9.0
* Getting gen_smtp (Hex package)
Checking package (https://s3.amazonaws.com/s3.hex.pm/tarballs/gen_smtp-0.9.0.tar)
Fetched package
Note:
Hexから取得できないときは、Githubから取得すればいい。
ElixirとErlangの違い
- アトム
# Elixir
:atom
# Erlang
atom.
- 文字列
# Elixir
"String"
# Erlang
'String'
Note:
Erlangの古いライブラリはバイナリに対応していない場合がある。
そのときは、文字リストに変換をする必要がある。(to_char_list/1関数を使う)
- 変数
# Elixir
x = 10
x1 = x + 1
# Erlang
X = 10.
X1 = X + 10.
Erlangのライブラリを使えれば確かに選択肢が広がるが、Erlangが使えることが必須条件。
じゃないと何かあったときに厳しい…(現実は非常なのです)
じゃないと何かあったときに厳しい…(現実は非常なのです)
エラーハンドリング
例外の取り扱い方について。
エラーハンドリング
raise/1を使ってエラーを出す。
iex> raise "Oops!!"
** (RuntimeError) Oops!!
iex> raise ArgumentError, message: "The argument value is invalid."
** (ArgumentError) The argument value is invalid.
try/rescueとパターンマッチを使って例外処理。
# 単一のパターンマッチ
iex> try do
...> raise "Oops!"
...> rescue
...> e in RuntimeError -> IO.puts("An error occurred: " <> e.message)
...> end
An error occurred: Oops!
:ok
# 複数のパターンマッチができる
iex> opts = [a: 1, b: 2, c: 3]
[a: 1, b: 2, c: 3]
iex> try do
...> opts
...> |> Keyword.fetch!(:source_file)
...> |> File.read!
...> rescue
...> e in KeyError -> IO.puts "missing :source_file option"
...> e in File.Error -> IO.puts "unable to read source file"
...> end
missing :source_file option
:ok
after
iex> try do
...> raise "Oops!"
...> rescue
...> e in RuntimeError -> IO.puts("An error occurred: " <> e.message)
...> after
...> IO.puts "The end!"
...> end
An error occurred: Oops!
The end!
:ok
ファイルとか開くときに使うらしい。
自作のエラー(defexception)
defmodule FastRelearning do
defexception message: "an example error has occurred"
end
iex> try do
...> raise FastRelearning
...> rescue
...> e in FastRelearning -> e
...> end
%FastRelearning{message: "an example error has occurred"}
throw/catch
iex> try do
...> for x <- 0..10 do
...> if x == 5, do: throw(x)
...> IO.puts x
...> end
...> catch
...> x -> "Caught: #{x}"
...> end
0
1
2
3
4
"Caught: 5"
exit
iex> spawn_link fn -> exit("Oops!") end
** (EXIT from #PID<0.141.0>) "Oops!"
Interactive Elixir (1.2.0) - press Ctrl+C to exit (type h() ENTER for help)
iex> try do
...> exit "Oops!"
...> catch
...> :exit, _ -> "exit blocked"
...> end
"exit blocked"
スーパーバイザーかプロセスの終了を処理させることが有効らしい。
実行可能ファイルについて
# mix.exs
defmodule FastRelearning.Mixfile do
use Mix.Project
def project do
[app: :fast_relearning,
version: "0.0.1",
elixir: "~> 1.2",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps,
escript: escript]
end
...
defp escript do
[main_module: FastRelearning]
end
end
# main module
defmodule FastRelearning do
def main(args \\ []) do
args
|> parse_args
|> response
|> IO.puts
end
defp parse_args(args) do
{opts, word, _} =
args |> OptionParser.parse(switches: [upcase: :boolean])
{opts, List.to_string(word)}
end
defp response({opts, "Hello"}), do: response({opts, "World"})
defp response({opts, word}) do
if opts[:upcase], do: word = String.upcase(word)
word
end
end
# 実行結果
> mix escript.build
Compiled lib/fast_relearning.ex
Generated fast_relearning app
Consolidated List.Chars
Consolidated String.Chars
Consolidated Collectable
Consolidated Enumerable
Consolidated IEx.Info
Consolidated Inspect
Generated escript fast_relearning with MIX_ENV=dev
> ls
README.md _build config deps doc fast_relearning lib mix.exs mix.lock test
> escript fast_relearning --upcase "Hello"
WORLD
> escript fast_relearning "Hi"
Hi
同時並行性(Process、Agent、Task)
Process
defmodule FastRelearning do
def add(x, y), do: IO.puts(x + y)endiex> spawn(FastRelearning, :add, [1, 2])3#PID<0.143.0>
- メッセージパッシング
defmodule FastRelearning do
def listen do
receive do
{:ok, "Hello"} -> IO.puts "World"
end
end
end
iex> pid = spawn(FastRelearning, :listen, [])
#PID<0.148.0>
iex> send pid, {:ok, "Hello"}
World
{:ok, "Hello"}
- プロセスのリンク
defmodule FastRelearning do
def explode, do: exit(:bom)endiex> spawn(FastRelearning, :explode, [])#PID<0.143.0>iex> spawn_link(FastRelearning, :explode, [])#PID<0.145.0>** (EXIT from #PID<0.141.0>) :bomInteractive Elixir (1.2.0) - press Ctrl+C to exit (type h() ENTER for help)iex>
defmodule FastRelearning do
def explode, do: exit(:bom) def run do
Process.flag(:trap_exit, true)
spawn_link(FastRelearning, :explode, [])
receive do
{:EXIT, _from_pid, reason} -> IO.puts "Exit reason: #{reason}"
end
end
end
iex> FastRelearning.run
Exit reason: bom
:ok
- プロセスの監視
defmodule FastRelearning do
def explode, do: exit(:bom) def run do
{_pid, _ref} = spawn_monitor(FastRelearning, :explode, [])
receive do
{:DOWN, _ref, :process, _from_pid, reason} -> IO.puts "Exit reason: #{reason}"
end
end
end
iex> FastRelearning.run
Exit reason: bom
:ok
Agent
状態維持のバックグラウンドを抽象化された機能を提供している。
iex> {:ok, agent} = Agent.start_link(fn -> [1, 2, 3] end)
{:ok, #PID<0.146.0>}
iex> Agent.update(agent, fn(state) -> state ++ [4, 5] end)
:ok
iex> Agent.get(agent, &(&1))
[1, 2, 3, 4, 5]
iex> agent
#PID<0.146.0>
iex> Agent.get(agent, fn(state) -> state end)
[1, 2, 3, 4, 5]
# 名前付きAgent
iex> Agent.start_link(fn -> [1,2,3] end, name: Number)
{:ok, #PID<0.154.0>}
iex> Agent.get(Number, &(&1))
[1, 2, 3]
Task
関数をバックグラウンドで実行し、後で実行した関数の戻り値を受け取る機能を提供している。
defmodule FastRelearning do
def double(x) do
:timer.sleep(2000)
x * 2
end
end
iex> task = Task.async(FastRelearning, :double, [2000])
%Task{owner: #PID<0.141.0>, pid: #PID<0.143.0>, ref: #Reference<0.0.3.32>}
iex> IO.puts "Hello world!!"
Hello world!!
:ok
iex> Task.await(task)
4000
OTPの同時並行性(GenServer、GenEvent)
GenServer
クライアント-サーバ関係のサーバを実現するためのbehaviourモジュール。
ElixirとOTPで一般的なサーバを構築するため、抽象化されている。
GenServerはプロセスであり他のElixirプロセスとして、状態を維持、非同期などのコードを実行するため使用することができる。
ElixirとOTPで一般的なサーバを構築するため、抽象化されている。
GenServerはプロセスであり他のElixirプロセスとして、状態を維持、非同期などのコードを実行するため使用することができる。
汎用サーバプロセス(GenServer)を使用することの利点
- GenServerモジュールを使用するとインタフェース機能の標準セットを持っている
- トレース及びエラー報告のための機能が含まれている
GenServerには、2つの機能が実装されている。
- クライアントAPI
- サーバのコールバック
defmodule FastRelearning do
use GenServer
@doc """
GenServer.init/1 Server Callback
"""
def init(state) do
{:ok, state}
end
@doc """
GenServer.handle_call/3 Server Callback
関数を呼びその返答を待つ同期処理
"""
def handle_call(:dequeue, _from, [value|state]) do
{:reply, value, state}
end
def handle_call(:dequeue, _from, []) do
{:reply, nil, []}
end
def handle_call(:queue, _from, state) do
{:reply, state, state}
end
@doc """
GenServer.handle_cast/2 Server Callback
関数を呼び出すが、呼び出しPIDを受け取らず、返答もしない非同期処理
"""
def handle_cast({:enqueue, value}, state) do
{:noreply, state ++ [value]}
end
@doc """
Client API
"""
def start_link(state \\ []) do
GenServer.start_link(__MODULE__, state, name: __MODULE__)
end
def queue do
GenServer.call(__MODULE__, :queue)
end
def dequeue do
GenServer.call(__MODULE__, :dequeue)
end
def enqueue(value) do
GenServer.cast(__MODULE__, {:enqueue, value})
end
end
iex> FastRelearning.start_link([1, 2, 3])
{:ok, #PID<0.143.0>}
iex> FastRelearning.queue
[1, 2, 3]
iex> FastRelearning.dequeue
1
iex> FastRelearning.queue
[2, 3]
iex> FastRelearning.enqueue(4)
:ok
iex> FastRelearning.queue
[2, 3, 4]
前に書いた記事: GenServerの基本を習得する
GenEvent
GenEventは、入ってくるイベントを受け取ってイベントを処理するプロセスに通知を行うイベントマネージャ。
イベントの流れへ動的にハンドラを追加や削除する機能を提供する。
イベントの流れへ動的にハンドラを追加や削除する機能を提供する。
defmodule LoggerHandler do
use GenEvent
def handle_event({:message, message}, messages) do
IO.puts "Logging new message: #{message}"
{:ok, [message|messages]}
end
def handle_call(:messages, messages) do
{:ok, Enum.reverse(messages), messages}
end
end
defmodule PersistenceHandler do
use GenEvent
def handle_event({:message, message}, state) do
IO.puts "Persisting log message: #{message}"
IO.puts "Save #{message}"
{:ok, state}
end
end
iex> {:ok, pid} = GenEvent.start_link([])
{:ok, #PID<0.152.0>}
iex> GenEvent.add_handler(pid, LoggerHandler, [])
:ok
iex> GenEvent.add_handler(pid, PersistenceHandler, [])
:ok
iex> GenEvent.notify(pid, {:message, "Hello World"})
:ok
Logging new message: Hello World
Persisting log message: Hello World
Save Hello World
iex> GenEvent.call(pid, LoggerHandler, :messages)
["Hello World"]
OTPのスーパーバイザ
他のプロセスを監視するプロセス。
スーパーバイザ(Supervisor)
ストラテジーの一覧
- :one_for_one - 失敗した子プロセスのみを再起動する。
- :one_for_all - 失敗したイベントの中にある全ての子プロセスを再起動する。
- :rest_for_one - 失敗したプロセス、そのプロセスより後に開始された全てのプロセスを再起動する。
- :simple_one_for_one - 動的にアタッチされた子プロセスに最適。スーパーバイザは、1つだけ子プロセスを含むことが要求される。
defmodule FastRelearning do
use GenServer
@doc """
GenServer.init/1 Server Callback
"""
def init(state) do
{:ok, state}
end
@doc """
GenServer.handle_call/3 Server Callback
関数を呼びその返答を待つ同期処理
"""
def handle_call(:dequeue, _from, [value|state]) do
{:reply, value, state}
end
def handle_call(:dequeue, _from, []) do
{:reply, nil, []}
end
def handle_call(:queue, _from, state) do
{:reply, state, state}
end
@doc """
GenServer.handle_cast/2 Server Callback
関数を呼び出すが、呼び出しPIDを受け取らず、返答もしない非同期処理
"""
def handle_cast({:enqueue, value}, state) do
{:noreply, state ++ [value]}
end
@doc """
Client API
"""
def start_link(state \\ []) do
GenServer.start_link(__MODULE__, state, name: __MODULE__)
end
def queue do
GenServer.call(__MODULE__, :queue)
end
def dequeue do
GenServer.call(__MODULE__, :dequeue)
end
def enqueue(value) do
GenServer.cast(__MODULE__, {:enqueue, value})
end
end
iex> import Supervisor.Spec
nil
iex> children = [
...> worker(FastRelearning, [], [name: FastRelearning])
...> ]
[{FastRelearning, {FastRelearning, :start_link, []}, :permanent, 5000, :worker,
[FastRelearning]}]
iex> {:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
{:ok, #PID<0.168.0>}
iex> FastRelearning.queue
[]
iex> FastRelearning.enqueue([1, 2])
:ok
iex> FastRelearning.queue
[[1, 2]]
iex> FastRelearning.dequeue
[1, 2]
iex> FastRelearning.queue
[]
スーパーバイザは入れ子にもできる。
Taskスーパーバイザ
Taskには専用のスーパーバイザがある。
内部では、:simple_one_for_oneを使っている。
内部では、:simple_one_for_oneを使っている。
iex> import Supervisor.Spec
nil
iex> children = [
...> supervisor(Task.Supervisor, [[name: TaskSupervisor]])
...> ]
[{Task.Supervisor, {Task.Supervisor, :start_link, [[name: TaskSupervisor]]},
:permanent, :infinity, :supervisor, [Task.Supervisor]}]
iex> {:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
{:ok, #PID<0.187.0>}
Note:
スーパーバイザが開始された状態で、start_child/2関数を使い監視されたタスクを作ることもできる。
Extra
in演算子について
iex> :a in [:a, :b]
true
iex> :b in [:a, :b]
true
iex> :c in [:a, :b]
false
Speaking to oneself
メタプログラミングは自分の記事見ればいいので割愛。
まぁでも大体、勘が戻ってきたかな。(勘違い?)
途中、Mailerのソースコードを読んでました。
何故…人は横道にそれるのだろうか…若さゆえの過ちか!認めたくないものだな…
何故…人は横道にそれるのだろうか…若さゆえの過ちか!認めたくないものだな…
明日はElixirからメールを送ってみます。
メール配信システムにおける一番最初の基礎(???)
メール配信システムにおける一番最初の基礎(???)
できれば、Erlangのライブラリを使って構築したかったのですが、
Erlangレベルがあまりにも低くソースが分からなかったので、
対象としたかったErlangライブラリを使っているMailerからまず使ってみることにします。
Erlangレベルがあまりにも低くソースが分からなかったので、
対象としたかったErlangライブラリを使っているMailerからまず使ってみることにします。
果たして明日中に自分へのメールが届くのだろうか!?