[Elixir]macroを書くときの影響範囲

ElixirのASTって、Lipsの文法をTupleで表現しているようなものなのですね。defmacroquoteMacro.expand_onceなんかで分解してみると、 {atom, [List], [List]}の形式で分解されていき、Lipsのような読み方で読み解くことができる、と。

なので、Elixirは

  • Elixirの文法そのままでHigh Levelな表現で処理を記述する
  • LipsのようなLow Levelな表現で処理系を記述する

という2層で処理を記述することができる、と。High/Lowは、処理の抽象度を指しています。

後者がMacroになるわけですが、そんなMacroも下手に適用範囲を広げないためにcontextによる適用範囲を限定する機能を持っています。

Elixir Hygiene

以下の通り、 __MODULE__ により、contextが異なることがわかります。
quote で囲まれるところは、つまるところ、呼び出された側のcontextに限定された環境下でのみ、有効になる、ということを意味する。

Elixirはこのようにcontextを設定することで、macroの影響範囲を限定する手段をとっています。

defmodule Macro do
  defmacro definfo do
    IO.puts "In macro's context: (#{__MODULE__})"

    quote do
      IO.puts "In caller's context: (#{__MODULE__})"

      def info do
        IO.puts """
        My name is #{__MODULE__}
        My functions are #{inspect __info__(:functions)}
        """
      end
    end
  end
end

defmodule MyModule do
  require Mod
  Macr.definfo
end


# In macro's context: (Elixir. Macr)
# In caller's context: (Elixir.MyModule)
# iex(1)> MyModule.info
# My name is Elixir.MyModule
# My functions are [info: 0]
#
# :ok

Violate hygiene!!

通常、 quote で囲まれた範囲は、quoteで囲まれた中だけで有効です。
それ以外の領域には影響を及ぼすことはありません。

例えば、以下の通り name の変数は Setter.bind_name のマクロの範囲を出ることはありません。

defmodule Setter do
  defmacro bind_name(string) do
    quote do
      name = unquote(string)
    end
  end
end
# iex(1)> require Setter
# nil
# iex(2)> name = "Neko"
# "Neko"
# iex(3)> Setter.bind_name "Inu"
# "Inu"
# iex(4)> name
# "Neko"

しかし、以下のように var!/1 を使うとこのhygieneな領域を超えて変数をOverrideすることが可能になります。この機能は、本当に必要なときのみ使いましょう、と強く書かれてもいます。

defmodule Setter do
  defmacro bind_name(string) do
    quote do
      var!(name) = unquote(string)
    end
  end
end
# iex(1)> require Setter
# nil
# iex(2)> name = "Neko"
# "Neko"
# iex(3)> Setter.bind_name "Inu"
# "Inu"
# iex(4)> name
# "Inu"

他メモ

Logger.debugは、productionのコンパイルが行われるときに削除される。


なるほど…
Macroは処理系を書き換えてしまって、例えば Kernel.if でさえ異なる処理に書き換えることもできるので少し慎重になっていましたが、影響範囲を限定的にできる、その領域を超えるときは明示が必要、という姿勢が良いですね。

ElixirのMacroのドキュメントに以下が書かれていることからも、やっぱり明示に寄せている方に寄せておきたいですね。

Remember that explicit is better than implicit. Clear code is better than concise code.


追記 20160321

Elixir1.2.3ですが、 varvar! のコードを添付。これを見ると、 var! ではASTを直接操作していることがわかります。なるほど。

広告

[Elixir]macroを書くときの影響範囲” への1件のフィードバック

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中