QLC for elixir
QLCとはerlangのリスト内包表記を使ったクエリ書式であり、リストやEts, Dets, Mnesiaなどに対応している。今回はこれをelixirで使えるようにしようと思う。
qlcの使い方
基本的には、erlangなので変数は大文字で、
[Expression || Qualifier1, Qualifier2, ...] Expression :: 任意のErlang式(template) Qualifier :: Filter or Generators Fiilter :: bool値を返すErlang式 Generator :: Pattern <- ListExpression ListExpression :: Qlc_handle or list()
わかりにくいかもしれないので、例を見てみる。
1> QH = qlc:q([{X,Y} || X <- [a,b], Y <- [1,2]]), qlc:eval(QH). [{a,1},{a,2},{b,1},{b,2}]
[a, b]と[1,2]の直積を計算している。
数学の集合の定義のように記述できるのが魅力である。
さて、これをelixirで実現するのだが、幸いqlc:string_to_handle/3というものがあるのでこれを使ってもよい。マニュアルにも、
'This function is probably useful mostly when called from outside of Erlang, for instance from a driver written in C.'
のように書いてある。しかし、erlangと同じbeam上に構築されたelixirがCと同様の土俵まで降りていくのは残念である。
:erl_scan.string/1, :erl_parse.parse_exprs/1
erlangには当然だが、スキャナとパーサが内蔵されている。これを使ってマクロにすることで、elixirのコンパイル時に文字列をerlang式としてパースすることができる。
elixirとちがいerlangの文字列は文字リストであることに注意すると、
def exprs(s) do c = String.to_char_list(s) {:ok, m, _} = :erl_scan.string(c) {:ok, [expr]} = :erl_parse.parse_exprs(m) expr end
で文字列をErlangのASTにすることができる。これを実行時に:qlc.transform_expression/2で変数をバインドすることでqlc_handleを作ることができる。
Macro.escape/1 の使い所
通常マクロはelixir ASTをハンドリングするのだが、今回はErlang ASTをhandleするので、ただのタプルとして取り扱って欲しい。そのためにMacro.escape/1
を使う。
quote はbind_quotedする
マクロ中ではいつものquoteを使うのだが、引数をバインドするためには unquoteよりもbind_quoted: [keyword]を使うのが主流だ。
quote bind_quoted: [exprl: exprl, bindings: bindings, opt: opt] do Qlc.expr_to_handle(exprl, bindings, opt) end
unquoteがないため実に見やすい。それだけではなく、評価の回数が保証されるため安全である。
これらを合わせてQlcマクロを作成すると、このようにかけることになる。
iex> require Qlc iex> list = [a: 1,b: 2,c: 3] iex> qlc_handle = Qlc.q("[X || X = {K,V} <- L, K =/= Item]", ...> [L: list, Item: :b]) ...> Qlc.e(qlc_handle) [a: 1, c: 3] ...> Qlc.q("[X || X = {K, V} <- L, K =:= Item]", ...> [L: qlc_handle, Item: :c]) |> ...> Qlc.e [c: 3]
ソースはqlcに置いてみた。