Elixirメタプログラミング 01: 内部表現 ー quote/2とunquote/1
Tags: elixir, webdev, tutorial, programming
Published: 2019-07-02 12:18:13

---
title: Elixirメタプログラミング 01: 内部表現 ー quote/2とunquote/1
published: true
description: Elixir公式サイトの許諾を得て「Quote and unquote」の解説にもとづき、加筆補正を加えて、Elixirで使えるメタプログラミング技術のうち内部表現の扱いについてご説明します。Elixirプログラムを独自のデータ構造で表す機能は、メタプログラミングの基本です。そのデータ構造は内部表現(quoted expression)と呼ばれます。
tags: #elixir, #webdev, #tutorial, #programming
---

本稿はElixir公式サイトの許諾を得て「[Quote and unquote](https://elixir-lang.org/getting-started/meta/quote-and-unquote.html)」の解説にもとづき、加筆補正を加えて、Elixirで使えるメタプログラミング技術のうち内部表現の扱いについてご説明します。Elixirプログラムを独自のデータ構造で表す機能は、メタプログラミングの基本です。そのデータ構造は内部表現(quoted expression)と呼ばれます。


# quote/2による式の表現
Elixirのプログラムで式は、3要素のタプルで組み立てられます。 式をタプルで返すのがマクロ[`quote/2`](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#quote/2)です。たとえば、関数sum(1, 2, 3)の呼び出しは、つぎのタプルで表されます。

```elixir
iex> quote do: sum(1, 2, 3)
{:sum, [], [1, 2, 3]}
```

タプルの第1要素は関数名、第2要素がメタデータを含むリスト、そして第3要素には引数がリストで示されます。

関数だけでなく、演算子も3要素のタプルで表せます。

```elixir
iex> quote do: 1 + 2
{:+, [context: Elixir, import: Kernel], [1, 2]}
```

マップも同じく3要素のタプルになります。

```elixir
iex> quote do: %{1 => 2}
{:%{}, [], [{1, 2}]}
```

さらに、変数の宣言もタプルで示されます。ただし、第3要素はアトムです。

```elixir
iex> quote do: x
{:x, [], Elixir}
```

より複雑な式も3要素タプルの組み合わせで表せます。そのとき、タプルが入れ子のツリー構造になることも少なくありません。たとえば、変数に値を代入したときです。

```elixir
iex> quote do: x = 1
{:=, [], [{:x, [], Elixir}, 1]}
```

多くの言語ではこのような表現を[抽象構文木(AST)](https://ja.wikipedia.org/wiki/%E6%8A%BD%E8%B1%A1%E6%A7%8B%E6%96%87%E6%9C%A8)と呼びます。Elixirの用語では内部表現(quoted expression)です。

```elixir
iex> quoted = quote do: sum(1, 2 + 3, 4)
{:sum, [], [1, {:+, [context: Elixir, import: Kernel], [2, 3]}, 4]}
```

内部表現をもとのコードの文字列表現にしたい場合もあるでしょう。そのときには、[`Macro.to_string/2`](https://hexdocs.pm/elixir/Macro.html#to_string/2)をお使いください(第2引数は関数でデフォルト値が`fn _ast, string -> string end`)。

```elixir
iex> Macro.to_string(quoted)
"sum(1, 2 + 3, 4)"
```

`quote/2`から返されるタプルの3要素は、つぎのような形式です。


```elixir
{atom | tuple, list, list | atom}
```

- 第1要素: アトムまたは3要素のタプル
- 第2要素: メタデータを納めたキーワードリスト
    - たとえば、数値やコンテキスト
- 第3要素: 呼び出された関数の引数リストまたはアトム
    - アトムの場合タプルは変数を意味する

つぎの5つのElixirリテラルは、`quote/2`の引数に渡したとき、タプルでなく値がそのまま返されます。

| リテラル |例  |
|:------ |:--|
| アトム | :sum |
| 数値　　| 1.0 |
| リスト　| [1, 2] |
| 文字列 | "string" |
| 2要素のタプル | {key, value} |

ほとんどのElixirのコードは、内部表現として表すことができます。たとえば、式のツリー構造が組み合わさった例です。

```elixir
iex> quote do: String.upcase("foo") 
{{:., [], [{:__aliases__, [alias: false], [:String]}, :upcase]}, [], ["foo"]}
```

`if/2`マクロの構文は、`do`/`end`ブロックを使わずに、ひとつの式で表せます。この場合、内部表現はつぎのとおりです。

```elixir
iex> quote do: if(true, do: :this, else: :that)
{:if, [context: Elixir, import: Kernel], [true, [do: :this, else: :that]]}
```

`do`/`end`ブロックを使った場合も、内部表現は変わりません。

```elixir
iex> quote do
...>   if true do
...>     :this
...>   else
...>     :that
...>   end
...> end
{:if, [context: Elixir, import: Kernel], [true, [do: :this, else: :that]]}
```


# unquote/1による式の値の取り出し
`quote/2`が返す内部表現は、ひとまとまりのコードを表します。たとえば、値が納められた変数を用いたとき、内部表現に示されるのは識別子です。

```elixir
iex> number = 13
13
iex> Macro.to_string(quote do: 11 + number)
"11 + number"
```

しかし、場合によっては変数値を確かめたい場合もあるでしょう。値を取り出して式に差し込むときに使うのは、[`unquote/1`](https://elixir-lang.org/getting-started/meta/quote-and-unquote.html)です。なお、このマクロは`quote/2`とともに用いなければなりません。

```elixir
iex> Macro.to_string(quote do: 11 + unquote(number))
"11 + 13"
```

関数に変数を渡して呼び出すと、内部表現の中に変数が入れ子のタプルで示されます。

```elixir
iex> quote do
...>   sum(1, number, 3)
...> end
{:sum, [], [1, {:number, [], Elixir}, 3]}
```

この場合も、変数を`unquote/1`に渡せば、変数値を取り出して内部表現に差し込めるのです。

```elixir
iex> quote do
...>   sum(1, unquote(number), 3)
...> end
{:sum, [], [1, 13, 3]}
```

また、アトムを`unquote/1`に渡して、名前をつけた関数が表せます。

```elixir
iex> func = quote do: unquote(:hello)(:world)
{:hello, [], [:world]}
iex> Macro.to_string(func)
"hello(:world)"
```

リスト内に別のリストの値を加えて、内部表現にしたいこともあるでしょう。この場合、`unquote/1`で差し込んだリストは、入れ子として示されます。

```elixir
iex> inner = [3, 4, 5]
[3, 4, 5]
iex> Macro.to_string(quote do: [1, 2, unquote(inner), 6])
"[1, 2, [3, 4, 5], 6]"
```

リストから要素を取り出して内部表現に加えたいとき用いるのが、[`unquote_splicing/1`](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#unquote_splicing/1)です。

```elixir
iex> Macro.to_string(quote do: [1, 2, unquote_splicing(inner), 6])
"[1, 2, 3, 4, 5, 6]"
```

このマクロは、リストだけでなく関数の引数としてリスト要素を加える場合にも使えます。

```elixir
iex> Macro.to_string(quote do: sum(1, 2, unquote_splicing(inner), 6))
"sum(1, 2, 3, 4, 5, 6)"
```

`unquote/1`と`unquote_splicing/1`は、マクロを扱うときにとても役立ちます。マクロを書く開発者が、ひとまとまりのコードを受け取って、そこにまた別のコードが差し込めるからです。コードを変換したり、記述して、コンパイル時にコードを生成するために用いられます。


# Macro.escape/2によるエスケープ
内部表現は、3要素のタプルを基本とします。たとえば、マップはそのままでは内部表現として使えません。4要素以上のタプルも同じです。そうした値は、内部表現に変えなければならないのです。

```elixir
iex> quote do: %{1 => 2}
{:%{}, [], [{1, 2}]}
```

[`Macro.escape/2`](https://hexdocs.pm/elixir/Macro.html#escape/2)を用いても内部表現は得られます。

```elixir
iex(23)> Macro.escape(%{1 => 2})
{:%{}, [], [{1, 2}]}
```

また、`Macro.escape/2`を使えば、変数から取り出した値が内部表現にできるのです。

```elixir
iex> map = %{hello: :world}
%{hello: :world}
iex> quote do: map
{:map, [], Elixir}
iex> quote do: unquote(map)
%{hello: :world}
iex> Macro.escape(map)
{:%{}, [], [hello: :world]}
```

マクロは内部表現を受け取り、内部表現を返さなければなりません。しかし、マクロの実行時に値を扱わなければならないことがあります。そのとき、値と内部表現はよく区別してください。Elixirの標準の値(リストやマップ、プロセス、参照など)と内部表現は分けて扱うことが大切なのです。

整数やアトム、および文字列は、その値を表す内部表現があります。そのほかに、たとえばマップは内部表現に変換しなければなりません。あるいは、関数や参照は値を内部表現にはできないのです。`quote/2`や`unquote/1`などについて詳しくは「[Kernel.SpecialForms](https://hexdocs.pm/elixir/Kernel.SpecialForms.html)」、また`Macro.escape/1`と関連する関数は「[Macro](https://hexdocs.pm/elixir/Macro.html)」モジュールをご参照ください。