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