嗯,是的,我又回来了。没有再继续在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执行了。