Elixir元编程 #

一、抽象语法树 #

1.1 AST结构 #

Elixir代码在内部表示为元组:

elixir
iex(1)> quote do: 1 + 2
{:+, [context: Elixir, imports: [{2, Kernel}]], [1, 2]}

iex(2)> quote do: x = 1
{:=, [], [{:x, [], Elixir}, 1]}

1.2 AST组成 #

AST元组结构:{operator, metadata, arguments}

elixir
iex(1)> ast = quote do: foo(1, 2, 3)
{{:., [], [{:__aliases__, [alias: false], [:foo]}]}, [], [1, 2, 3]}

1.3 基本类型 #

基本类型保持原样:

elixir
iex(1)> quote do: 42
42

iex(2)> quote do: :atom
:atom

iex(3)> quote do: "string"
"string"

二、AST操作 #

2.1 Macro.to_string #

elixir
iex(1)> ast = quote do: 1 + 2 * 3
{:+, [], [1, {:*, [], [2, 3]}]}

iex(2)> Macro.to_string(ast)
"1 + 2 * 3"

2.2 Macro.var #

elixir
iex(1)> Macro.var(:x, nil)
{:x, [], nil}

2.3 Macro.escape #

elixir
iex(1)> Macro.escape([1, 2, 3])
[1, 2, 3]

iex(2)> Macro.escape(%{a: 1})
{:%{}, [], [a: 1]}

2.4 Macro.expand #

elixir
iex(1)> ast = quote do: Kernel.is_atom(:hello)
iex(2)> Macro.expand(ast, __ENV__)
true

三、遍历AST #

3.1 Macro.prewalk #

前序遍历:

elixir
iex(1)> ast = quote do: 1 + 2 * 3
iex(2)> Macro.prewalk(ast, fn node -> IO.inspect(node); {node, nil} end)

3.2 Macro.postwalk #

后序遍历:

elixir
iex(1)> Macro.postwalk(ast, fn node -> IO.inspect(node); {node, nil} end)

3.3 修改AST #

elixir
defmodule Transformer do
  def double_numbers(ast) do
    Macro.prewalk(ast, fn
      n when is_number(n) -> n * 2
      other -> other
    end)
  end
end

四、代码生成 #

4.1 动态函数定义 #

elixir
defmodule DynamicFunctions do
  Enum.each([:foo, :bar, :baz], fn name ->
    def unquote(name)(), do: unquote(name)
  end)
end

4.2 动态模块创建 #

elixir
defmodule ModuleFactory do
  def create_module(name, functions) do
    module_name = Module.concat([name])

    Module.create(module_name, quote do
      unquote_splicing(
        Enum.map(functions, fn {name, body} ->
          quote do
            def unquote(name)(), do: unquote(body)
          end
        end)
      )
    end, Macro.Env.location(__ENV__))
  end
end

4.3 结构体生成 #

elixir
defmodule StructBuilder do
  defmacro defdata(fields) do
    quote do
      defstruct unquote(fields)

      def new(attrs \\ %{}) do
        struct(__MODULE__, attrs)
      end
    end
  end
end

五、编译时钩子 #

5.1 @before_compile #

elixir
defmodule Tracer do
  defmacro __before_compile__(env) do
    functions = Module.definitions_in(env.module, :def)

    quote do
      def __functions__, do: unquote(Macro.escape(functions))
    end
  end
end

defmodule Example do
  @before_compile Tracer

  def foo, do: 1
  def bar, do: 2
end

5.2 @on_definition #

elixir
defmodule MethodTracker do
  def __on_definition__(env, kind, name, args, guards, body) do
    IO.puts("#{kind} #{name}/#{length(args)}")
  end
end

defmodule Example do
  @on_definition MethodTracker

  def foo(a, b), do: a + b
end

5.3 @after_compile #

elixir
defmodule Example do
  @after_compile __MODULE__

  def __after_compile__(env, bytecode) do
    IO.puts("Module compiled!")
  end
end

六、模块属性注入 #

6.1 注册属性 #

elixir
defmodule MyAttr do
  Module.register_attribute(__MODULE__, :callbacks, accumulate: true)

  defmacro callback(name) do
    quote do
      @callbacks {unquote(name), __ENV__.line}
    end
  end
end

6.2 使用累积属性 #

elixir
defmodule Example do
  import MyAttr

  callback :before_save
  callback :after_save

  def callbacks, do: @callbacks
end

七、元编程最佳实践 #

7.1 保持简单 #

elixir
defmodule Good do
  defmacro simple do
    quote do: 1 + 1
  end
end

defmodule Bad do
  defmacro complex do
    # 避免复杂的元编程逻辑
    ...
  end
end

7.2 提供文档 #

elixir
defmodule MyMacro do
  @doc """
  Documentation for the macro.

  ## Examples

      iex> MyMacro.example()
      :ok

  """
  defmacro example do
    ...
  end
end

7.3 测试宏 #

elixir
defmodule MyMacroTest do
  use ExUnit.Case

  test "macro expansion" do
    assert Macro.expand(quote(do: MyMacro.example()), __ENV__) == expected_ast
  end
end

八、总结 #

本章学习了:

特性 用途
quote 代码转AST
unquote 注入值
Macro.prewalk 前序遍历
Macro.postwalk 后序遍历
@before_compile 编译前钩子
@on_definition 定义钩子

准备好学习类型规范了吗?让我们进入下一章。

最后更新:2026-03-27