Elixirで正規表現
Elixir の正規表現は Regexモジュール を使う。
仕事ではPHPを書くことが多い。PHPの正規表現は PCRE関数 が標準的だろう。普段使うPCRE関数をElixirのRegex で置き換えるとしたらどうなるのか?
比較の対象は preg-match と preg_replace の二つにする。理由は、その二つをよく使うので (その二つくらいしか使わないとも言う)。
preg_match と比べて
単純なマッチ
単純にマッチするかどうかを判定する場合, Regex.match?/2 を使う。
<?php echo preg_match('/foo/', 'foo'); // => 1
iex> Regex.match?(~r/foo/, "foo") true
名前なしキャプチャ
名前なしキャプチャを利用する場合, Regex.run/3 を使う。preg_match はキャプチャした結果は変数の参照を介して受け取る。Regex の場合 run/3 の返り値として受けとる。
一度マッチしたらそこで処理は打ち切られる。打ち切られたくない場合は Regex.scan/3 を使う (PHP なら preg_match_all)。
<?php preg_match('/c(d)/', 'abcd', $m); print_r($m); // => Array // ( // [0] => cd // [1] => d // )
iex> Regex.run(~r/c(d)/, "abcd") ["cd", "d"]
名前つきキャプチャ
名前つきキャプチャは Regex.named_captures/3。Regex.named_captures/3 はキャプチャされた部分のみの結果になる。
<?php preg_match('/c(?<foo>d)/', "abcd", $m); print_r($m); // => Array // ( // [0] => cd // [foo] => d // [1] => d // )
iex> Regex.named_captures(~r/c(?<foo>d)/, "abcd") %{"foo" => "d"}
preg_replace と比べて
Regex.replace で置換を行う。引数の順序が違うので慣れで書くと間違う。
<?php echo preg_replace('/b/', 'd', 'abc').PHP_EOL; // => adc echo preg_replace('/\.(\d)$/', '.${1}0', '500.5'); // => 500.50
iex> Regex.replace(~r/b/, "abc", "d") "adc" iex> Regex.replace(~r/\.(\d)$/, "500.5", ".\\g{1}0") "500.50"
感想
PHP でできていたことは Elixir でもできる(まあ、PCRE互換だし)。
PHPの場合はパラメータによって一つの関数の中で機能をon/offしたりする感じだけれど、Elixir は機能ごとに関数を用意する感じ。
Elixirのドキュメント生成方法
mix.ex の deps に ex_doc と earmark を追加する。バージョンは最新版で。
defp deps do [{:ex_doc, ">= 0.0.0", only: :docs}, {:earmark, ">= 0.0.0", only: :docs}] end
パッケージのダウンロードとコンパイル
$ mix deps.get $ mix deps.compile
環境変数に docs を設定して、mix docs コマンドを実行する。
$ MIX_ENV=docs mix docs
出来上がり。
ドキュメントを hex に上げる。
$ MIX_ENV=docs mix hex.docs
prefecture_jp というライブラリを作成した
prefecture_jpというライブラリを作成した。Hexにも上げた。
機能
都道府県コードから都道府県名への変換、またはその逆。都道府県コードは JIS X 0401 に対応(0埋め2桁の数字)。
使い方
単体で
iex> PrefectureJp.find("01").name "北海道" iex> PrefectureJp.find(code: "13").name "東京都"
iex> PrefectureJp.find(name: "東京都").code "13"
都道府県情報の一覧
iex> PrefectureJp.all [%PrefectureJp.Prefecture{area: "北海道", code: "01", name: "北海道", name_e: "hokkaido", name_h: "ほっかいどう", name_k: "ホッカイドウ"}, . . . %PrefectureJp.Prefecture{area: "九州", code: "47", name: "沖縄県", name_e: "okinawa", ...}]
Ectoと一緒に
use PrefectureJp, 都道府県コードを持つカラム名
と書くことで、以下のように都道府県コードから都道府県名を求めることができる。
defmodule Place do use Ecto.Model use PrefectureJp, :prefecture_code schema "places" do field :prefecture_code, :string end end
iex> place = %Place{prefecture_code: "23"} iex> place |> Place.prefecture(:name) "愛知県"
その他
このライブラリは jp_prefectureに大きな影響を受けている。
Elixir v1.1.0
v1.1.0 がリリースされていた (下書きしているときは、v1.1.0 だったけれど、現在の最新版は v1.1.1)。
機能追加で特記すべきことは、Enum
, Dict
, Task
への関数の追加と、MapSet
型の追加、と書かれている。HashSet
よりも速く、今後はSet
の実装はMapSet
がメインになっていくっぽい。英語がよく理解できていない。ついでに言うと HashSet
と MapSet
の違いもよくわかっていない。
今後は MapSet
を使うようにすればいいのかな。
Elixir1.0.5 は Erlang18 に未対応ではなかった
というのを初めて知った。
きっかけは、dialyxir を使おうと思ったから。
dialyxir をインストールする時の必ずエラーが発生しインストールに失敗する。 エラーの内容は以下の通り。
Creating PLT /home/ymmtmsys/.dialyxir_core_18_1.0.5.plt ... ** (FunctionClauseError) no function clause matching in Keyword.get/3 (elixir) lib/keyword.ex:118: Keyword.get(:no_such_file, :files, nil) lib/mix/tasks/dialyzer.plt.ex:132: Mix.Tasks.Dialyzer.Plt.core_plt_contains?/2 lib/mix/tasks/dialyzer.plt.ex:123: anonymous fn/1 in Mix.Tasks.Dialyzer.Plt.missing_apps/0 (elixir) lib/enum.ex:662: anonymous fn/3 in Enum.filter/2 (elixir) lib/enum.ex:1261: Enum."-reduce/3-lists^foldl/2-0-"/3 (elixir) lib/enum.ex:662: Enum.filter/2 lib/mix/tasks/dialyzer.plt.ex:106: Mix.Tasks.Dialyzer.Plt.need_add?/0 lib/mix/tasks/dialyzer.plt.ex:57: Mix.Tasks.Dialyzer.Plt.run/1
:no_such_file
ということなのでファイルが存在していなくエラーになっているっぽいが。。。よくわからないのでググってみると
こんなissueを発見。そしてコメントに
と書かれているのを発見。
...あれ、今まで動いていたから気付いていなかったけど、まだ Erlang18 は対応してないっぽい。
Elixir の github を覗いてみると、確かにv1.2.0でErlan1 18.0に対応するようだ
とりあえず、自分の環境の Erlang のバージョンを下げよう。
追記 (2015/09/15)
インストールに失敗する原因は、Erlang のバージョンではなくメモリ不足によるものだった。 512Mのメモリを積んだVM上でインストールして失敗を繰り返していたたけれど、2Gに増やしたら成功した。 ちなみに1Gでは失敗した。
自分のPCは4GBしか積んでないので、VMに2Gも割けないのでインストールが終わったら通常の512Mに戻した。
追記 (2015/09/29)
https://github.com/elixir-lang/elixir/releases/tag/v1.0.5 を見るとErlang18.0 に対応したと書いてありました。
HTTPoisonのプーリング
HTTPクライアントにHTTPoisonを利用してみた。
同時実行数を増やすと50コネクション以上のコネクションは作成されないことがわかった。コードを見てみると、内部ではhackneyが使われていることがわかった。
hackney のドキュメントをみると、何も指定がなければデフォルトのプールが利用されるようだ(これ?)。
変更するためにはどうしたらよいかを調べた。
プールの数を変更する方法
名前をつけてプールを開始する。
poolname = :mypool options = [max_connections: 10] :hackney_pool.start_pool(poolname, options)
HTTPリクエストを送る際のパラメータで、[hackney: [pool: :mypool]]
を渡す。
HTTPoison.get!("http://localhost:4001", %{}, hackney: [pool: :mypool])
これでデフォルトではなく、自分の希望通りのプールサイズになる。
調べた時のライブラリのバージョン
- httpoison, 0.7.2
- hackney, 1.3.1
Phoenix で Poolboy を使ってコネクション(プロセス)プーリング
Poolboy を使ってコネクションプーリングができるようにする。接続先は memcached。
サンプルのhello_phoenixを元にした。手を加えたファイルだけ以下に貼っておく (ちなみに Phoenix は v1.0.0)。
実行してみと、、、
% curl localhost:4000 {"val":"Hello Phoenix"}
成功。
メモ
- mcd はプロセスを生成した時点ではmemcached と接続されていない。ので、start_link してすぐに使おうとすると、結果が取れない。
max_overflow
の値を0以上にすると、プールしている数がsize
に達していてもさらにそこからmax_overflow
分だけプロセスを生成する。max_overflow
分のプロセスは必要になった時点で生成されるので、01 で書いたとおり、コネクションが確立されていないプロセスが渡される時がある(というか、全てそうだった)。