読者です 読者をやめる 読者になる 読者になる

もやもやエンジニア

IT系のネタで思ったことや技術系のネタを備忘録的に綴っていきます。フロント率高め。

Elixir 入門 ~ META-PROGRAMMING IN ELIXIR ~ その1 - Quote and unquote

Elixir Macro

Quoting

  • Elixir のプログラムはtupleを伴った3つの要素に置き換えることができる。例えばsum(1, 2, 3)があった場合は以下のように書き換えることができる。
  • 以下、自分の環境だとsumなんかおらん!と言われるのでremとか適当なビルトイン関数に置き換えて試すといいかと。
{:sum, [], [1, 2, 3]}
  • quote マクロを使うと式の構造を見ることができる
iex> quote do: sum(1, 2, 3)
{:sum, [], [1, 2, 3]}
  • 1つ目の要素は関数名のatom、2つ目はメタデータのリスト、3つ目は引数のリストとなる
  • operatorも同様に確認できる。
iex> quote do: 1 + 2
{:+, [context: Elixir, import: Kernel], [1, 2]}
  • mapも同様に確認できる。
iex> quote do: %{1 => 2}
{:%{}, [], [{1, 2}]}
  • 変数も同様
iex> quote do: x
{:x, [], Elixir}
  • Macro.toString/1を使えば式を文字列で取得できる。
iex> Macro.to_string(quote do: sum(1, 2 + 3, 4))
"sum(1, 2 + 3, 4)"

Unquoting

  • Quote で式の構造を見ることができるが、場合によっては展開して欲しくない部分もあるかもしれない。以下の例を見てみる。
iex> number = 13
iex> Macro.to_string(quote do: 11 + number)
"11 + number"
  • numberがそのままstringとして扱われていて、欲しい結果ではない。numberに値を注入するためにはunquoteを使う。
iex> number = 13
iex> Macro.to_string(quote do: 11 + unquote(number))
"11 + 13"
  • さきほどとは異なり、unquoteで囲んだnumberは束縛されている値が表示された。
  • unquoteは関数名にも適用できる。
iex> fun = :hello
iex> Macro.to_string(quote do: unquote(fun)(:world))
"hello(:world)"
  • funhello/1で展開された。
  • Listの中に新しい要素を追加したい場合を考えてみる。
iex> inner = [3, 4, 5]
iex> Macro.to_string(quote do: [1, 2, unquote(inner), 6])
"[1, 2, [3, 4, 5], 6]"
  • これは期待した結果ではない。このような場合は unquote_splicing を使うとよい。
iex> inner = [3, 4, 5]
iex> Macro.to_string(quote do: [1, 2, unquote_splicing(inner), 6])
"[1, 2, 3, 4, 5, 6]"
  • Unquotingはコードの中に別のコードを埋め込むことができ、マクロを書くときにとても便利なので覚えておこう。

Escaping

  • Macro.escape/1を使っても展開できる。
iex> map = %{hello: :world}
iex> Macro.escape(map)
{:%{}, [], [hello: :world]}
  • マクロはquoted expressionsを受け取りquoted expressionsを返す必要がある。大事なのはexpressionvalueを区別することである。integerやatom、stringといったようなvalueはquoted expressionsが自分自身の値だが、mapのような値は変換される必要がある。
  • ちょっと訳が怪しいけど。。。おそらく以下のようにquoteをかけるとquoted expressionsが返るものと自身の値が返るもので違いがあるので気をつけろということかな。
iex> quote do: %{1=> 2}
{:%{}, [], [{1, 2}]}
iex> quote do: :hello
:hello