Elixirで正規表現

Elixir の正規表現Regexモジュール を使う。

仕事ではPHPを書くことが多い。PHP正規表現PCRE関数 が標準的だろう。普段使うPCRE関数をElixirのRegex で置き換えるとしたらどうなるのか?

比較の対象は preg-matchpreg_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_docearmark を追加する。バージョンは最新版で。

  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がメインになっていくっぽい。英語がよく理解できていない。ついでに言うと HashSetMapSet の違いもよくわかっていない。

今後は 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を発見。そしてコメントに

Elixir does not yet support Erlang 18.

と書かれているのを発見。

...あれ、今まで動いていたから気付いていなかったけど、まだ 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

memcached のクライアントにはmcdを使った。

サンプルのhello_phoenixを元にした。手を加えたファイルだけ以下に貼っておく (ちなみに Phoenix は v1.0.0)。

実行してみと、、、

% curl localhost:4000
{"val":"Hello Phoenix"}

成功。

メモ

  1. mcd はプロセスを生成した時点ではmemcached と接続されていない。ので、start_link してすぐに使おうとすると、結果が取れない。
  2. max_overflow の値を0以上にすると、プールしている数が size に達していてもさらにそこから max_overflow 分だけプロセスを生成する。
  3. max_overflow 分のプロセスは必要になった時点で生成されるので、01 で書いたとおり、コネクションが確立されていないプロセスが渡される時がある(というか、全てそうだった)。

感想

  • 既存ライブラリのおかげで簡単にプーリングできた。
  • Poolboy, mcd 両方とも、Erlang のライブラリ。Elixir を学ぶなら、Erlang を勉強しないとだめっぽい(動作を理解するのと、ライブラリの情報)。