[elixir! #56] 模块编译顺序

距离写上一篇文章已经过去了将近1年时间。

使用elixir进行元编程的时候,初学者经常会不清楚模块的编译顺序。我们用一个简单的例子来解释一下。

defmodule M1 do

    @after_compile M0
    @before_compile M0
    use M0

    @a 1
    @a 2

end

我们定义了一个 M1 模块,它使用了几个 elixir 内置的模块属性:

  • @after_compile M0 表示在 M1 模块编译之后,调用 M0.__after_compile__
  • @before_compile M0 表示在 M1 模块编译之前,调用 M0.__before_compile__

接着我们定义 M0 模块, 在每个hook被调用时打印相关的信息:

defmodule M0 do

    defmacro __using__(_) do
        mod = __CALLER__.module
        Module.register_attribute mod, :a, accumulate: true
        IO.puts "#{__MODULE__} being used by #{mod}"
    end

    defmacro __after_compile__(_, _) do
        IO.puts "#{__MODULE__} after compile be called"
    end

    defmacro __before_compile__(_) do
        IO.puts "#{__MODULE__} before compile be called"
        Module.get_attribute(__CALLER__.module, :a)
        |> IO.inspect(label: "#{__CALLER__.module} has attribute a")
    end

end

惊人的一幕发生了,我们看到了这样的顺序:

Elixir.M0 being used by Elixir.M1
Elixir.M0 before compile be called
Elixir.M1 has attribute a: [2, 1]
Elixir.M0 after compile be called

结论是,在 elixir 编译器编译一个模块的时候,hooks 按照这样的顺序被调用的:

  1. __using__
  2. module attributes 被编译
  3. __before_compile__
  4. __after_compile__

你可能感兴趣的