Elixir宏 #
一、宏基础 #
1.1 什么是宏 #
宏是编译时代码转换的机制,允许在编译时生成和修改代码。
1.2 定义宏 #
elixir
defmodule MyMacros do
defmacro say_hello(name) do
quote do
IO.puts("Hello, #{unquote(name)}!")
end
end
end
1.3 使用宏 #
elixir
defmodule Example do
require MyMacros
def greet do
MyMacros.say_hello("World")
end
end
二、quote与unquote #
2.1 quote #
quote 将代码转换为AST(抽象语法树):
elixir
iex(1)> quote do: 1 + 2
{:+, [context: Elixir, imports: [{2, Kernel}]], [1, 2]}
iex(2)> quote do: x = 1
{:=, [], [{:x, [], Elixir}, 1]}
2.2 unquote #
unquote 在quote内部注入值:
elixir
iex(1)> value = 42
42
iex(2)> quote do: unquote(value)
42
iex(3)> name = :foo
:foo
iex(4)> quote do: def unquote(name)(), do: unquote(value)
{:def, [context: Elixir, imports: [{2, Kernel}]],
[{:foo, [context: Elixir], Elixir}, [do: 42]]}
2.3 unquote_splicing #
展开列表:
elixir
iex(1)> values = [1, 2, 3]
[1, 2, 3]
iex(2)> quote do: [0, unquote_splicing(values), 4]
[0, 1, 2, 3, 4]
三、宏参数 #
3.1 AST参数 #
宏参数是AST:
elixir
defmodule MyMacros do
defmacro log(expr) do
quote do
IO.puts("Expression: #{inspect(unquote(Macro.escape(expr)))}")
result = unquote(expr)
IO.puts("Result: #{inspect(result)}")
result
end
end
end
3.2 使用示例 #
elixir
require MyMacros
MyMacros.log(1 + 2)
# Expression: {:+, [], [1, 2]}
# Result: 3
# 3
四、宏实践 #
4.1 定义函数宏 #
elixir
defmodule AttrReader do
defmacro defattr(name, default \\ nil) do
quote do
def unquote(name)(), do: @attrs[unquote(name)] || unquote(default)
def unquote(:"#{name}!")() do
@attrs[unquote(name)] || raise "#{unquote(name)} not set"
end
end
end
end
4.2 使用函数宏 #
elixir
defmodule Config do
require AttrReader
@attrs %{host: "localhost", port: 8080}
AttrReader.defattr(:host)
AttrReader.defattr(:port)
AttrReader.defattr(:ssl, false)
end
Config.host()
Config.port()
Config.ssl()
4.3 条件宏 #
elixir
defmodule MyIf do
defmacro my_if(condition, do: do_clause, else: else_clause) do
quote do
case unquote(condition) do
true -> unquote(do_clause)
false -> unquote(else_clause)
end
end
end
end
4.4 测试宏 #
elixir
defmodule TestMacros do
defmacro assert_equal(left, right) do
quote do
left = unquote(left)
right = unquote(right)
unless left == right do
raise "Assertion failed: #{inspect(left)} != #{inspect(right)}"
end
end
end
end
五、宏与模块 #
5.1 使用use #
elixir
defmodule MyModule do
defmacro __using__(opts) do
quote do
import MyModule
@my_option unquote(opts[:option])
end
end
def my_function, do: "Hello"
end
defmodule Example do
use MyModule, option: "value"
end
5.2 动态定义 #
elixir
defmodule DynamicModule do
def create(name, functions) do
Module.create(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
六、宏最佳实践 #
6.1 保持简单 #
elixir
defmodule Good do
defmacro simple do
quote do: 1 + 1
end
end
defmodule Bad do
defmacro complex do
# 避免过于复杂的宏
...
end
end
6.2 提供文档 #
elixir
defmodule MyMacro do
@doc """
Documentation for the macro.
## Examples
iex> MyMacro.example()
:ok
"""
defmacro example do
quote do: :ok
end
end
6.3 使用Macro.escape #
elixir
defmacro inspect_ast(ast) do
escaped = Macro.escape(ast)
quote do
IO.puts("AST: #{inspect(unquote(escaped))}")
end
end
七、常见宏模式 #
7.1 属性累积 #
elixir
defmodule Callback do
defmacro __using__(_opts) do
quote do
Module.register_attribute(__MODULE__, :callbacks, accumulate: true)
import Callback
end
end
defmacro callback(name) do
quote do
@callbacks unquote(name)
end
end
end
7.2 代码注入 #
elixir
defmodule Inject do
defmacro __before_compile__(env) do
callbacks = Module.get_attribute(env.module, :callbacks)
quote do
def callbacks, do: unquote(callbacks)
end
end
end
八、调试宏 #
8.1 Macro.expand #
elixir
iex(1)> ast = quote do: MyMacro.example()
iex(2)> Macro.expand(ast, __ENV__)
8.2 Macro.to_string #
elixir
iex(1)> ast = quote do: 1 + 2 * 3
iex(2)> Macro.to_string(ast)
"1 + 2 * 3"
九、总结 #
本章学习了:
| 特性 | 用途 |
|---|---|
defmacro |
定义宏 |
quote |
代码转AST |
unquote |
注入值 |
__using__ |
use回调 |
__before_compile__ |
编译前钩子 |
准备好学习Sigils了吗?让我们进入下一章。
最后更新:2026-03-27