スポンサーリンク

2015年8月3日

[Elixir]quoteとunquoteを使ってみる

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

Goal

quoteとunquoteを使ってみる。

Dev-Environment

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

Wait a minute

Elixirのメタプログラミングで使うquoteとunquoteを使ってみます。
Elixirでメタプログラミンをやるために必要な基礎的な話とのこと。
Elixirの内部では、式がどのような形で表現されるのか知る内容ですね。

Index

Quote and unquote
|> Quoting
|> Unquoting
|> Escaping

Quoting

quoteについて軽くまとめる。
関数の表現のされ方。
iex(1)> quote do: sum(1, 2, 3)
{:sum, [], [1, 2, 3]}
Elixirプログラムにおけるビルディングブロックは、3つの要素を持つタプル。
演算子の表現のされ方。
iex(2)> quote do: 1 + 2
{:+, [context: Elixir, import: Kernel], [1, 2]}
+が関数部分に当たり、1, 2は引数リストになる。
context: Elixirは、ErlangVM上で動作するからどの言語なのか判別するためにあると思われる。
import: Kernelは、importするモジュールの指定ですかね。
お次は、Mapの表現のされ方。
iex(3)> quote do: %{1 => 2}
{:%{}, [], [{1, 2}]}
変数の表現のされ方。
iex(4)> quote do: x
{:x, [], Elixir}
少し複雑な式をquoteしてみる。
iex(5)> quote do: sum(1, 2 + 3, 4)
{:sum, [], [1, {:+, [context: Elixir, import: Kernel], [2, 3]}, 4]}
Macro.to_stringを使って、quoteされた式がどのように出力されるか見てみる。
iex(6)> Macro.to_string(quote do: sum(1, 2 + 3, 4))
"sum(1, 2 + 3, 4)"
上記の少しだけ複雑な式だが、以下の形式で構成されている。
{atom | tuple, list, list | atom}
  • 最初の要素、アトムまたは同じ表現の別のタプル
  • 2番目の要素、数字やコンテキストのようなメタデータを含むキーワードリスト
  • 3番目の要素、関数呼び出しのための引数かアトム。(アトムの場合、変数を表現するタプルであることを意味している)
どっかで見たことある気がする表現・・・あぁ構文木だ。
タプルで表現してるけど、構文木に似てるんだ。

Unquoting

unquoteについて軽くまとめ。
変数や関数を値として出力してくれない場合がある。
だから、それらを出力させるためにunquoteを使うといいらしい。
実例を見た方が早いです。
されない例。
iex(7)> number = 1
1
iex(8)> Macro.to_string(quote do: 1 + number)
"1 + number"
numberの部分を値で出したい。
iex(10)> Macro.to_string(quote do: 1 + unquote(number))
"1 + 1"
Macro.to_stringなしでやってみた。
iex(11)> quote do: 1 + number
{:+, [context: Elixir, import: Kernel], [1, {:number, [], Elixir}]}
iex(12)> quote do: 1 + unquote(number)
{:+, [context: Elixir, import: Kernel], [1, 1]}
関数名を入れてみる。
iex(13)> fun = :hello
:hello
iex(14)> Macro.to_string(quote do: unquote(fun)(:world))
"hello(:world)"
iex(15)> Macro.to_string(quote do: fun(:world))
"fun(:world)"
unquoteでは望んだ結果が得られない時がある。
[1,2,6]に[3,4,5]のリストを途中に入れて、
[1,2,3,4,5,6]のリストを作りたい。
でもやってみると・・・
iex(16)> inner = [3, 4, 5]
[3, 4, 5]
iex(17)> Macro.to_string(quote do: [1, 2, unquote(inner), 6])
"[1, 2, [3, 4, 5], 6]"
違う、そうじゃない。そうしたいわけじゃない。
これを解決してくれるのが、unquote_splicing と言うマクロ(?)。
iex(18)> Macro.to_string(quote do: [1, 2, unquote_splicing(inner), 6]
"[1, 2, 3, 4, 5, 6]"

Escaping

Macro.escape/1について何ですけど・・・英語の説明が分からない。
マクロはquoteで囲まれた式を受け取り、quoteで囲まれた式を返す必要がある。
しかし場合によっては、マクロの実行に値とquoteで囲まれた式の間で区別が必要になる。
とりあえず、実例だけ実行してみる。
これは、quoteできる。
iex(19)> quote do: %{hello: :world}
{:%{}, [], [hello: :world]}
でも変数に入った途端・・・できない。
iex(20)> map = %{hello: :world}
%{hello: :world}
iex(21)> quote do: map
{:map, [], Elixir}
unquoteを試してみるが・・・望んだ結果は得られず。
iex(22)> quote do: unquote(map)
%{hello: :world}
しかし、Macro.escape/1を使えばできる。
iex(23)> Macro.escape(map)
{:%{}, [], [hello: :world]}
他の値も、マップのように明示的に変換する必要がある。
(リスト、マップ、プロセス、リファレンス、などなど)

Speaking to oneself

正直言っていいですか?
理解できた気がしない!!ってか、さっぱり分からん・・・orz
どこに基礎的な部分があったのかも、さっぱり分からん。
分かったのは、内部ではそういった形で表現されているのね、って程度ですね。
マクロを使ってみれば少しは分かるでしょうか?
私にはまだ早かったようです。
しかし、メタプログラミングの項目を一通り実施してみます。
どうせ、項目数は多くないですし・・・

Bibliography

人気の投稿