スポンサーリンク

2016年2月8日

ElixirSchoolに入校したメモ(2日目)

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

Wait a minute

Elixir Shool入校2日目。
Advancedに関してのメモ。

Index

Elixir Schoolに入校したメモ(2日目)
|> 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
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プロセスとして、状態を維持、非同期などのコードを実行するため使用することができる。
汎用サーバプロセス(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を使っている。
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からまず使ってみることにします。
果たして明日中に自分へのメールが届くのだろうか!?

Bibliography

人気の投稿