Elixir: Macro入門

# 概要

Elixir の Macro の基礎的なことをまとめました。

# 動機

```Ecto.Query.select``` を使って次のようなコードを書いたところエラーが発生しました。

```ex:ecto_select.ex
RssFeed
|> select([f], %{id: f.id})
```

```bash:error
** (CompileError) ecto_select.ex:19: function f/0 undefined
```

たしかに、```f``` を定義していないですが、[ドキュメント](http://hexdocs.pm/ecto/Ecto.Query.html#select/3) のとおりで、前に同じように書いたときは動いていました。

Ecto のバージョンを確認したり、いろいろ調べた結果、```import Ecto.Query```を忘れていただけという単純なミスでした。
しかし、定義されていない ```f``` が使えていることが理解できていないことに気づき調べました。

# マクロ

```Ecto.Query.select``` のコードを確認するとマクロであることが分かります。
使う側からすると見た目が似ている関数とマクロですが、実際は渡ってくるパラメータや戻り値の意味が大きく異なります。

同じものをマクロと関数で実装し確認します。

```ex:sampe1.exs
defmodule Sample do
  defmacro sample_macro(param) do
    IO.puts "### sample_macro ###"
    IO.inspect param
    ""
  end

  def sample_func(param) do
    IO.puts "### sample_func ###"
    IO.inspect param
    ""
  end
end

defmodule Main do
  require Sample
  import Sample

  def main() do
    x = 1
    sample_macro(x > 0)
    sample_func(x > 0)
  end
end

Main.main

```

```bash:結果
### sample_macro ###
{:>, [line: 21], [{:x, [line: 21], nil}, 0]}
### sample_func ###
true
```

関数では ```x > 0``` が実行されたあと、関数に渡されるので ```param``` は ```true``` になります。関数に渡されるタイミングで実行される必要があるため、```x = 0``` を消すと ```function x/0 undefined``` になります。
こちらは普段良く使っているため、理解しやすいと思います。

マクロの方は ```{:>, [line: 21], [{:x, [line: 21], nil}, 0]}``` という ```x > 0``` という構文が解析された結果が渡ります。
この形式を [AST (abstract syntax tree、抽象構文木)](https://ja.wikipedia.org/wiki/%E6%8A%BD%E8%B1%A1%E6%A7%8B%E6%96%87%E6%9C%A8) と言います。
構文が渡るだけなので、```x = 0``` を消してもこちらはエラーになりません。

次に少し修正して、受け取った ```param``` を返します。

```ex:sample1-1.ex
defmodule Sample do
  defmacro sample_macro(param) do
    IO.puts "### sample_macro ###"
    IO.inspect param
    param
  end

  def sample_func(param) do
    IO.puts "### sample_func ###"
    IO.inspect param
    param
  end
end

defmodule Main do
  require Sample
  import Sample

  def main() do
    x = 1
    IO.puts "result = #{sample_macro(x > 0)}"
    IO.puts "result = #{sample_func(x > 0)}"
  end
end

Main.main
```

```bash:結果
### sample_macro ###
{:>, [line: 21], [{:x, [line: 21], nil}, 0]}
result = true
### sample_func ###
true
result = true
```

両方とも結果は ```true``` になりました。 関数の ```param``` は ```true``` なので、```true``` を返すと ```true``` になることは分かります。

マクロの ```param``` は AST です。```{:>, [line: 21], [{:x, [line: 21], nil}, 0]}``` という AST を返して ```true``` が得られるということから、マクロでは AST を返し **それを実行した結果** が呼び出し元に返されることが分かります。

# 関数を実行する AST

```{:関数名, [], nil}``` で関数が呼べます。

こちらのサンプルのように、```Sample``` モジュールは ```Main``` モジュールの ```hello``` にはアクセスしていませんが、```hello``` を呼ぶ AST を返すことで呼び出し元の ```hello``` を実行できます。

```ex:sample2.exs
defmodule Sample do
  defmacro sample() do
    {:hello, [], nil}
  end
end

defmodule Main do
  require Sample
  import Sample

  def hello() do
   IO.inspect "hello"
  end

  def main() do
    sample()
  end
end

Main.main
```

呼び出し元に関数を実装しておいて、モジュールからはその関数を呼ぶ AST を返すことでコールバックのようなこともできます。（意図せず呼ばれてしまうと分かりやすいとは言えないですが、、、）

AST については [ドキュメント](http://elixir-lang.org/getting-started/meta/quote-and-unquote.html) を参照してください。 ```quote``` などを使うことで確認できます。

# DSL

実行した結果ではなく構文が渡るので、実行できなくても構文的に正しければ、マクロのパラメータとして渡すことができます。

```ex:sampe3.exs
defmodule Sample do
  defmacro sample(args) do
    IO.inspect args
    ""
  end
end

defmodule Main do
  require Sample
  import Sample

  def main() do
    sample(this is a pen)
    sample(this is a <b>new</b> pen)
  end
end

IO.inspect Main.main
```

上の例のように、Elixir の構文として正しければ、マクロに渡せます。
逆に構文として正しくない、```this is a pen.``` （ドットがついている）や、```this is a <i><b>new</b></i> pen``` （```><```という構文がない）はエラーになります。

この仕組みを使って、（構文が Elixir であれば）独自の言語を作ることができます。これを [DSL (domain-specific language、ドメイン固有言語)](https://ja.wikipedia.org/wiki/%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E5%9B%BA%E6%9C%89%E8%A8%80%E8%AA%9E) と言います。

誰が見ても分かりやすい DSL を作ることで生産性や可読性が高まりますが、Elixir 本来の動作と変わるため、理解を難しくすることもあります。むやみに独自 DSL 作るのは避けた方が良さそうです。

# 何でもできる

マクロを使えば呼び出し元で実行する構文を返せるので、多分、何でもできると思います。
例えば、呼んだだけで呼び出し元の変数を変更するマクロもできます。

こちらの例では、```updatex``` が呼ばれると ```x``` が勝手に ```3``` にセットされてしまいます。

```ex:sample4.exs
defmodule Sample do
  defmacro updatex() do
    {:=, [], [{:x, [], nil}, 3]}
  end
end

defmodule Main do
  require Sample
  import Sample

  def main() do
    x = 0
    IO.puts "(before) x=#{x}"
    updatex()
    IO.puts "(after) x=#{x}"
  end
end

IO.inspect Main.main
```

予測できないところで、変数の変更や関数の呼び出しが起こると混乱の元になるので、使い方に注意が必要です。

# 最後に

マクロはとても強力な機能ですが、よく言われるように[「大いなる力には、大いなる責任が伴う」](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%91%E3%82%A4%E3%83%80%E3%83%BC%E3%83%9E%E3%83%B3) ということを忘れないようにしていきたいです。


# 参考

* [Macros - Elixir](http://elixir-lang.org/getting-started/meta/macros.html)
* [Quote and unquote - Elixir](http://elixir-lang.org/getting-started/meta/quote-and-unquote.html)
* [Domain Specific Languages - Elixir](http://elixir-lang.org/getting-started/meta/domain-specific-languages.html)
* [Understanding Elixir Macros, Part 1 - Basics](http://www.theerlangelist.com/article/macros_1)