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