スポンサーリンク

2015年9月17日

[Elixir]外部コマンドを叩いてみよう!!

とある錬金術師の万能薬(Elixir)

Goal

Elixirから外部のコマンドを実行する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.5

Wait a minute

Elixirから外部コマンドのgitを叩いてみる。
gitのバージョンとヘルプの出力結果を取得する。
ここで言っている外部コマンドの定義。
外部コマンド = Elixirの外にあるコマンド
(External command = Outside of Elixir)

Index

External command from Elixir
|> Find commad
|> Execute commad
|> Extra
|> Consideration

Find commad

Elixirの外部にあるコマンドが存在するか確認します。

Example:

外部コマンドが存在しない場合。
nilが返ってくる。
iex> System.find_executable("hogehoge")
nil
外部コマンドが存在する場合。
パスが返ってくる。
iex> System.find_executable("git")
"path/to/git.exe"

Execute commad

Elixirから外部コマンドを実行してみます。

Example:

まず、dateコマンドを叩いてみます。
iex> System.cmd("date", [])
{"Thu Sep 17 12:00:04     2015\n", 0}
戻ってくるのはタプルのようです。
続いて、gitコマンドでバージョンを取得してみましょう。
iex> System.cmd("git", ["--version"])
{"git version 2.1.0\n", 0}
ついでに、ヘルプオプションを呼び出してみる。
iex> {result, 0} = System.cmd("git", ["--help"])
{"usage: git [--version] [--help] [-C <path>] [-c name=value]\n           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n           [-p|--paginate|--no-pager] [--
no-replace-objects] [--bare]\n           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n           <command> [<args>]\n\nThe most commonly used git commands are:\n
 add        Add file contents to the index\n   bisect     Find by binary search the change that introduced a bug\n   branch     List, create, or delete branches\n   checkout   Chec
kout a branch or paths to the working tree\n   clone      Clone a repository into a new directory\n   commit     Record changes to the repository\n   diff       Show changes betwee
n commits, commit and working tree, etc\n   fetch      Download objects and refs from another repository\n   grep       Print lines matching a pattern\n   init       Create an empt
y Git repository or reinitialize an existing one\n   log        Show commit logs\n   merge      Join two or more development histories together\n   mv         Move or rename a file
, a directory, or a symlink\n   pull       Fetch from and integrate with another repository or a local branch\n   push       Update remote refs along with associated objects\n   re
base     Forward-port local commits to the updated upstream head\n   reset      Reset current HEAD to the specified state\n   rm         Remove files from the working tree and from
 the index\n   show       Show various types of objects\n   status     Show the working tree status\n   tag        Create, list, delete or verify a tag object signed with GPG\n\n'g
it help -a' and 'git help -g' lists available subcommands and some\nconcept guides. See 'git help <command>' or 'git help <concept>'\nto read about a specific subcommand or concept
.\n",
 0}
iex> IO.puts result
usage: git [--version] [--help] [-C <path>] [-c name=value]
           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
           [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
           <command> [<args>]

The most commonly used git commands are:
   add        Add file contents to the index
   bisect     Find by binary search the change that introduced a bug
   branch     List, create, or delete branches
   checkout   Checkout a branch or paths to the working tree
   clone      Clone a repository into a new directory
   commit     Record changes to the repository
   diff       Show changes between commits, commit and working tree, etc
   fetch      Download objects and refs from another repository
   grep       Print lines matching a pattern
   init       Create an empty Git repository or reinitialize an existing one
   log        Show commit logs
   merge      Join two or more development histories together
   mv         Move or rename a file, a directory, or a symlink
   pull       Fetch from and integrate with another repository or a local branch
   push       Update remote refs along with associated objects
   rebase     Forward-port local commits to the updated upstream head
   reset      Reset current HEAD to the specified state
   rm         Remove files from the working tree and from the index
   show       Show various types of objects
   status     Show the working tree status
   tag        Create, list, delete or verify a tag object signed with GPG

'git help -a' and 'git help -g' lists available subcommands and some
concept guides. See 'git help <command>' or 'git help <concept>'
to read about a specific subcommand or concept.

:ok

Extra

組み合わせて使ってみる。

Example:

gitコマンドのブランチを取得してみる。
iex> cmd_path = System.find_executable("git")
"path/to/git.exe"
iex> if cmd_path do
...>   {result, 0} = System.cmd(cmd_path, ["branch"])
...> end
{"  ecto_update\n  filling_in_layout\n  following_users\n* master\n  modeling_users\n  sign_in_out\n  sign_up\n  static_pages\n  updating_users\n  user_microposts\n",
 0}
iex> IO.puts result
  ecto_update
  filling_in_layout
  following_users
* master
  modeling_users
  sign_in_out
  sign_up
  static_pages
  updating_users
  user_microposts

:ok
コマンドが見つからなければnilが返ってくるのを利用して、
if文でチェックできますね。

Consideration

考察という名のメモ。
  • mkdirを叩くと処理が戻ってこない
ディレクトリは作成されているが、処理が戻ってこない。
おそらく、結果が返ってくるタイプのコマンドでないと使えない気がする。
もしくは、cmd/3関数のオプションか何かで方法があると思われる。
詳しくは調べていないため、現状不明。
“mix phoenix.new”する時にディレクトリも作っていたと思います。
そこら辺を調べてもいいかもしれないですね。
  • pingを叩くとbitstringが返ってくる
gitコマンドだと、人間の目で確認しやすい文字列で返ってくるのに、
pingを叩くとbitstringが返ってくる…この差異は何?
(コマンドプロンプトの文字コードの設定がutf8じゃないから?)
ここら辺は変換する方法があるので、必要になったら調べる程度。
  • もう一つの方法
Erlangの:os.cmd/1関数を使えば、外部コマンドを叩くことができるようです。(未検証)
…このコマンド、”mix phoenix.new”のソースファイル内で見た気がする。

Speaking to oneself

少し検証が不足している部分があるが、必要になったら調べれば良し!
これで外部コマンドを呼び出して結果が取得できる!!

Bibliography

2015年9月16日

[Elixir]Streamの使い方を習得する

とある錬金術師の万能薬(Elixir)

Goal

Streamの使い方を習得する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.5

Wait a minute

今回はStreamの回。
遅延評価について学ぶとまではいかないが、基本的な使い方を習得する。
その他、Enumableについて(申し訳程度に)少しだけやる。

Index

Stream
|> What is Stream?
|> Let’s use the Stream!

What is Stream?

怠惰(lazy)?遅延評価?Streamって何よ?
別にパイプ演算子をつなげたり、eachな処理をしたいならEnumなんかを使えばよくね?
しかし、Enumにも不都合(?)な点がある。
  • Enumで処理すると、全ての操作で中間リストが作られる点
例えば、以下のような実行をしたとする。
  • 1: 1~100までの範囲(Range)のリストが作られる
  • 2: 偶数のみのリストが作られる
  • 3: 全ての要素を足し合わせた結果が作られる

Example:

iex> 1..100 |> Enum.filter(fn(x) -> rem(x, 2) == 0 end) |> Enum.sum
2550
上記の実行では、途中途中の中間リスト(結果)が作成され次の処理に渡されている。
大概の場合、問題を感じることもないと思う。
私も感じたことはない。大したことをやってないとも言えるが(笑)
だが、非常に量が大きかったり、無限なものを扱うとなると適切ではないようです。
そこで、Streamと言うものが出てくる。
Streamは、一連の処理として作成できる。
そして、Enumオブジェクトへ渡した時にだけ実行される。
つまり、結果を作っているのではなく、処理を作っていると言える。
1~4の足し算を例に取って、それぞれのイメージをしてみる。
  • Enumのイメージ
    逐一、結果に対して処理をするイメージ。
1
1 + 2 = 3
3 + 3 = 6
6 + 4 = 10
  • Streamのイメージ
    一括で結果を出すイメージ。
(((1 + 2) + 3) + 4) = 10

Example:

EnumとStreamを実行した時の出力を見てみよう。
iex> range = 1..100
1..100
iex> Stream.map(range, &(&1 * 2))
#Stream<[enum: 1..100, funs: [#Function<45.113986093/1 in Stream.map/2>]]>
iex> Enum.map(range, &(&1 * 2))
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42,
 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82,
 84, 86, 88, 90, 92, 94, 96, 98, 100, ...]
どっちが良いではなく、上手く使い分けましょうってだけだと思います。
知識として取り入れるのは難しくないと思いますが、使い方や使い道を考えるのは結構大変そうですね(汗)

Note:

ついでに、少しEnumableについてまとめておく。
  • プロトコルである
  • ポリモーフィックである
  • Enumerableプロトコルを実装していれば、Enumerableできるデータ型で動作する

Let’s use the Stream!

Streamを使ってみましょう。
実行結果が同じで、つまらないと思いますが、
まずEnumの中間リストでやった内容をStreamに書き直してみましょう。

Example:

iex> 1..100 |> Stream.filter(fn(x) -> rem(x, 2) == 0 end) |> Enum.sum
2550
Stream.filter/2の段階では実行されていない。
だから、遅延処理と言われるわけですね。(納得)
ちょっとした注意点があります。
無限に繰り返すStreamの関数には、一部のEnumの関数へ渡してはいけないものがあります。
例えば、Stream.cycle/1と言った関数があるのですが、
名前の通り、無現に循環するStreamを作成します。

Example:

iex> Stream.cycle([1,2,3]) |> Enum.take(10)
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1]
iex> Stream.cycle([1,2,3]) |> Enum.take(100)
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2,
 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, ...]
1~3を繰り返すStreamをEnum.sum/1関数に渡したらどうなるでしょうか?
試しに実行しましたが、処理が終了しませんでした(笑)
無限ループに入ってしまうことがあるので、
Enumへ渡すStreamが有限なのか無限なのか考えるべきでしょう。
Streamに関してはここまでとします。
詳しい使い方が知りたければ、ドキュメントを見ることを推奨します。

Speaking to oneself

やる前は無限を扱うだの遅延評価だのと、
身構えてしまったけど、使うだけ、覚えるだけならそんなに難しくないですね。
しかし、使いこなすのはかなり難しそうですが(汗)

Bibliography

2015年9月15日

[Elixir]GenServerの基本を習得する

とある錬金術師の万能薬(Elixir)

Goal

GenServerの基本を習得する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.5

Wait a minute

プロセスやAgentをやってからしばらく経ちました。
忘れつつありますね。
と言うわけで、そろそろGenServerをやりましょう。
(文脈は犠牲になったのだ…)

Index

GenServer
|> Before you start
|> What is GenServer?
|> Check Function
|> Management of name
|> Extra

Before you start

Agentの記事で作ったプロジェクトを使います。
まだ実施していない方は、以前の記事を行って下さい。(コピペでもいいですが(笑))
基本的なAgentの使い方を習得する

What is GenServer?

GenServerって何ですか?
Getting Startを読んでもよく分からんかった。
なので、hexdocsのドキュメントを読みに行く。
GenServerは、
クライアント-サーバ関係のサーバを実現するためのbehaviourモジュールです。
ElixirとOTPで一般的なサーバを構築するため、抽象化されています。
GenServerはプロセスであり、他のElixirプロセスとして、
状態を維持、非同期などのコードを実行するため使用することができます。
汎用サーバプロセス(GenServer)を使用することの利点としては、
このモジュールを使用すると、インタフェース機能の標準セットを持っていると言うことです。
また、トレース及びエラー報告のための機能が含まれていることです。
GenServerには、2つの機能が実装されている。
  • クライアントAPI
  • サーバのコールバック

Check Function

頭の中が???状態なので、各機能に関してドキュメントを見てみる。
ドキュメントの方に凄く簡単なサンプルがあるので、まずこれを実行してみる。

Example:

サーバ側のコールバックを実装。
defmodule Stack do
  use GenServer

  def handle_call(:pop, _from, [h|t]) do
    {:reply, h, t}
  end

  def handle_cast({:push, item}, state) do
    {:noreply, [item|state]}
  end
end

Result:

実行結果。
iex> {:ok, pid} = GenServer.start_link(Stack, [:hoge])
{:ok, #PID<0.91.0>}
iex> GenServer.call(pid, :pop)
:hoge
iex> GenServer.cast(pid, {:push, :huge})
:ok
iex> GenServer.call(pid, :pop)
:huge
続き…クラッシュさせてみた。
関数にマッチしないとか言われてる。
iex> GenServer.call(pid, :pop)
** (EXIT from #PID<0.89.0>) an exception was raised:
    ** (FunctionClauseError) no function clause matching in Stack.handle_call/3
        (kv) lib/gen_server_example.ex:4: Stack.handle_call(:pop, {#PID<0.89.0>, #Reference<0.0.0.542>}, [])
        (stdlib) gen_server.erl:607: :gen_server.try_handle_call/4
        (stdlib) gen_server.erl:639: :gen_server.handle_msg/5
        (stdlib) proc_lib.erl:237: :proc_lib.init_p_do_apply/3

Interactive Elixir (1.0.5) - press Ctrl+C to exit (type h() ENTER for help)

14:47:53.085 [error] GenServer #PID<0.91.0> terminating
Last message: :pop
State: []
** (exit) an exception was raised:
    ** (FunctionClauseError) no function clause matching in Stack.handle_call/3
        (kv) lib/gen_server_example.ex:4: Stack.handle_call(:pop, {#PID<0.89.0>, #Reference<0.0.0.542>}, [])
        (stdlib) gen_server.erl:607: :gen_server.try_handle_call/4
        (stdlib) gen_server.erl:639: :gen_server.handle_msg/5
        (stdlib) proc_lib.erl:237: :proc_lib.init_p_do_apply/3
iex>
クラッシュして -> すぐ復帰…なんだこれ。
すごいですね…Elixirを壊すコードとか、さっぱり思いつかない。
他の言語でも思いつきませんが(笑)
本気でそれをやるなら、ErlangVMを壊すことを考えないといけない気がしますが…
閑話休題…それぞれの関数を確認してみる。

Client API

今回使ってるクライアントAPIの関数は…
  • start_link/3
  • call/3
  • cast/2
GenServerの開始。現在のプロセスにリンクプロセスを開始する。
多くの場合、監視ツリーの一部としてGenServerを開始するために使用される。
サーバが起動されると、それを初期化するために与えられた引数を渡して、
特定のモジュール内のinit/1関数(コールバック)を呼び出す。
(init/1が戻るまで、同期始動手順を確実にするため、この関数は戻らない)

- call/3

callは、同期した要求を送信する。サーバの応答かタイムアウトを待つ必要がある。
(call ↔ handle_call)

- cast/2

castは、非同期の要求を送信する。サーバの応答は送信されない。
(cast -> handle_cast)
この関数は、宛先ノードまたはサーバが存在しないに関係なく、すぐに:okが返される。
但し、アトムとしてサーバが指定されない限り。
クライアントAPIは他にもあるが…今回使っているものだけ書いています。

6 callbacks

定義できるコールバックは以下の6つ…
  • init/1
  • handle_call/3
  • handle_cast/2
  • handle_info/2
  • terminate/2
  • code_change/3

- init/1

サーバの起動時に呼び出される初期化用のコールバック。

- handle_call/3

同期要求に使用する、デフォルトの選択肢。

- handle_cast/2

非同期要求に使用する、応答を求めない時。

- handle_info/2

send/2でのメッセージ、call/2、cast/2を介して送信されないサーバが受信することができる全てのメッセージで使用する。

- terminate/2

サーバが終了しようとしている時に呼び出される。
クリーンアップに有用とのこと。
戻り値に:okを返す必要がある。

- code_change/3

ホット・コード・スワップ。
アプリケーションコードをライブアップグレードする時に呼び出される。
(Phoenixのライブリロードで使われてそうな気がする…)
それぞれのコールバックにおける戻り値は、ドキュメントを参照して下さい。

Note:

send/2で送信されたメッセージで予期しないメッセージがサーバへ到着する可能性がある。
それらのメッセージでクラッシュさせたい場合、Supervisorが必要になる。
予期しないメッセージが不具合を引き起こす可能性は割と高いとのこと。

Management of name

elixir-langのGetting Startにある、GenServerのサンプルを作成してみる。

Example:

サンプルソースを作成します。
defmodule KV.Registry do
  use GenServer

  ## Client API

  def start_link(opts \\ []) do
    GenServer.start_link(__MODULE__, :ok, opts)
  end

  def lookup(server, name) do
    GenServer.call(server, {:lookup, name})
  end

  def create(server, name) do
    GenServer.cast(server, {:create, name})
  end

  def stop(server) do
    GenServer.call(server, :stop)
  end

  ## Server Callbacks

  def init(:ok) do
    names = HashDict.new
    refs = HashDict.new
    {:ok, {names, refs}}
  end

  def handle_call({:lookup, name}, _from, {names, _} = state) do
    {:reply, HashDict.fetch(names, name), state}
  end

  def handle_call(:stop, _from, state) do
    {:stop, :normal, :ok, state}
  end

  def handle_cast({:create, name}, {names, refs}) do
    if HashDict.has_key?(names, name) do
      {:noreply, {names, refs}}
    else
      {:ok, pid} = KV.Bucket.start_link()
      ref = Process.monitor(pid)
      refs = HashDict.put(refs, ref, name)
      names = HashDict.put(names, name, pid)
      {:noreply, {names, refs}}
    end
  end

  def handle_info({:DOWN, ref, :process, _pid, _reason}, {names, refs}) do
    {name, refs} = HashDict.pop(refs, ref)
    names = HashDict.delete(names, name)
    {:noreply, {names, refs}}
  end

  def handle_info(_msg, state) do
    {:noreply, state}
  end
end

Example:

実行するためのテストを作成します。
defmodule KV.RegistryTest do
  use ExUnit.Case, async: true

  setup do
    {:ok, registry} = KV.Registry.start_link
    {:ok, registry: registry}
  end

  test "spawns buckets", %{registry: registry} do
    assert KV.Registry.lookup(registry, "shopping") == :error

    KV.Registry.create(registry, "shopping")
    assert {:ok, bucket} = KV.Registry.lookup(registry, "shopping")

    KV.Bucket.put(bucket, "milk", 1)
    assert KV.Bucket.get(bucket, "milk") == 1
  end

  test "removes buckets on exit", %{registry: registry} do
    KV.Registry.create(registry, "shopping")
    {:ok, bucket} = KV.Registry.lookup(registry, "shopping")
    Agent.stop(bucket)
    assert KV.Registry.lookup(registry, "shopping") == :error
  end
end
init/3は、{:ok, state}を返している。
handle_call/3は、{:reply, reply, new_state}を返している。
handle_cast/2は、{:noreply, new_state}を返している。
lookup/2とcreate/2の関数はサーバへ要求を送信する必要がある。
要求は、handle_call/3かhandle_cast/2の最初の引数で指定する。
複数の引数を提供するため、大体の場合はタプルで指定する。
例えば…
GenServer.call/2でサーバへ要求を送信している。
handle_call/3でサーバ側の応答を行っている。
そして、call/2の第二引数とhandle_call/3の第一引数は同じ。
def lookup(server, name) do
  GenServer.call(server, {:lookup, name})
end
def handle_call({:lookup, name}, _from, names) do
  {:reply, HashDict.fetch(names, name), names}
end

Extra

プロセスIDをモニタリングしてみる。

Example:

spawnで生み出したpidをモニタリングする。
iex> pid = spawn fn -> "hoge" end
#PID<0.94.0>
iex> Process.monitor(pid)
#Reference<0.0.0.569>
Agentで生み出したpidをモニタリングする。
iex> {:ok, pid} = Agent.start_link(fn -> HashDict.new end)
{:ok, #PID<0.91.0>}
iex> Process.monitor(pid)
#Reference<0.0.0.564>

Note:

自分でプロセスを生み出すのは、あまり良いことではないとのこと。
ならどうするのかと言うと、Supervisorを使うとのこと。
はよ、Supervisorやれってことですね。分かりました。

Speaking to oneself

すごい曖昧には分かった気がする…多分、ある程度は使える。
でも、モヤモヤ感が頭の中に渦巻いてる。
この言葉に言い表せない微妙な感じ…よくあることだな。
マクロを最初にやった時もこんな感じだった…大丈夫使っていれば覚えるさ~
とりあえず、Elixir in ActionにProcessで実装(?)しているGenServerっぽいもののソースコードがある。
まだ理解が追いつかないので、これも実践して試してみる。
Elixir in ActionのソースコードはGithubにアップされているみたいです。
各章毎にソースコードが管理されているようなので、クローンすればローカルでも勉強できます。
参考: Github - sasa1977/elixir-in-action
しかし、これ…一歩踏み込むとすぐErlangが出てくるな。
Erlangは文法さえ怪しいレベルです…ソースコードが読めない。

Bibliography

人気の投稿