Elixir中的一些展开顺序

开头要说的一些话

嗯,是的,我又回来了。没有再继续在https://mate.im  写文章的很大原因是,SEO没做好,好多文章搜索引擎检索不到。这个是需要一个长久时间投入才能做好的事情,目前忙着做一个项目,就先放放啦。

万事皆有因

最近在用Elixir和Phoenix在写一个“大”项目(其实就是一个项目下,多个小项目啦)。在写整个项目的时候发现数据模块要公用,然后呢,就单独提出来一个模块。不过怎么看都不顺眼,就去找github上寻找大神的作品,果然没有失望,找到该作品https://github.com/wojtekmach/github_ecto。满心欢喜的打开工程,然后整个人都木在那里了(Elixir刚开始用,要不要这么虐)。

深入探索

好啦,那么就开始阅读etco的代码了,然后依然被虐。。。。。

不废话了,先给出两个代码例子

defmodule MyModule do
  use MyPlugBuilder

  plug :hello
  plug :world, good: :morning
end
defmodule MyPlugBuilder do

  defmacro __using__(_opts) do
    quote do
      import MyPlugBuilder, only: [plug: 1, plug: 2]
      Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
      @before_compile MyPlugBuilder
    end
  end

  defmacro plug(plug, opts \\ []) do
    quote do
      @plugs {unquote(plug), unquote(opts)}
    end
  end

  defmacro __before_compile__(env) do
    plugs = Module.get_attribute(env.module, :plugs)
    quote do
      def plugs, do: unquote(plugs)
    end
  end
end

第一阶段展开

defmodule MyModule do
  # ----
  # use MyPlugBuilder
  # ---- ↓
  require MyPlugBuilder
  MyPlugBuilder.__using__([])
  # ----

  plug :hello
  plug :world, good: :morning
end

 此时,MyModule 会请求引入 MyPlugBuilder,接着会调用__using__() 宏,并且默认参数为[]

第二阶段展开

defmodule MyModule do
  require MyPlugBuilder

  # ----
  # MyPlugBuilder.__using__([])
  # ---- ↓
  import MyPlugBuilder, only: [plug: 1, plug: 2]
  Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
  @before_compile MyPlugBuilder
  # ----

  plug :hello
  plug :world, good: :morning
end

__using__函数会立刻被执行,相当于立刻将MyPlugBuilder的函数引入进来,并且给MyModule注册了一个叫做plugs的模块属性,同时告诉编译器,稍后编译MyPlugBuilder的时候,调用__before_compile__

第三阶段展开

defmodule MyModule do
  require MyPlugBuilder

  import MyPlugBuilder, only: [plug: 1, plug: 2]
  Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
  @before_compile MyPlugBuilder

  # ----
  # plug :hello
  # plug :world, good: :morning
  # ---- ↓
  @plugs {:hello, []}
  @plugs {:world, [good: :morning]}
  # ----
end

此时还没有展开__before_compile__,而是先展开从MyPlugBuilder模块中import进来的plug宏

第四阶段展开

defmodule MyModule do
  require MyPlugBuilder

  import MyPlugBuilder, only: [plug: 1, plug: 2]
  Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
  @before_compile MyPlugBuilder

  @plugs {:hello, []}
  @plugs {:world, [good: :morning]}

  MyPlugBuilder.__before_compile__(__ENV__)end

此时展开了MyPlugBuilder中__before_compile__宏,完成整个展开过程。

再深一步探索

如果我们把MyPlugBuilder改成下面这样会发生什么

defmodule MyPlugBuilder do

  defmacro __using__(_opts) do
    quote do
      import MyPlugBuilder, only: [plug: 1, plug: 2, aplug: 1]
      Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
      IO.puts __MODULE__
      IO.puts unquote(__MODULE__)
      @before_compile MyPlugBuilder
      unquote(defs())
    end
  end

  # `plug` 本体
  defmacro plug(plug, opts \\ []) do
    quote do
      IO.puts unquote(plug)
      @plugs {unquote(plug), unquote(opts)}
    end
  end

  defmacro aplug(plug) do
      xplug(plug, [])
  end

  defp defs() do
    IO.puts "aplug"
    quote unquote: false do
      IO.puts "eval"
      var!(pplug, MyPlugBuilder) = fn resource ->
        IO.puts resource
      end
    end
  end

  defp xplug(plug,opts \\ []) do
    quote do
      plug = unquote(plug)
      var!(pplug, MyPlugBuilder).(plug)
    end
  end
  
  defmacro __before_compile__(env) do
    plugs = Module.get_attribute(env.module, :plugs)
    IO.puts "__before_compile__"
    conn = compile(env)
    quote do
      def plugs, do: unquote(plugs)
      def plug_builder_call(unquote(conn)), do: IO.puts conn
    end
  end

  def compile(env) do
    conn = quote do: conn
    conn
  end

end

第一阶段展开

defmodule MyModule do
  # ----
  # use MyPlugBuilder
  # ---- ↓
  require MyPlugBuilder
  MyPlugBuilder.__using__([])
  # ----

  plug :hello
  plug :world, good: :morning
end

第二阶段展开

defmodule MyModule do
  require MyPlugBuilder

  # ----
  # MyPlugBuilder.__using__([])
  # ---- ↓
  MyPlugBuilder.defs()
  import MyPlugBuilder, only: [plug: 1, plug: 2, aplug: 1]
  Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
  IO.puts __MODULE__
  IO.puts "MyPlugBuilder"
  @before_compile MyPlugBuilder
  # ----

  plug :hello
  plug :world, good: :morning
end

需要注意的是,MyPlugBuilder的defs()函数先于两个IO.puts执行了。


你可能感兴趣的:(元编程,elixir,黑科技,翻译深化)