You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何无需quote直接获取作为参数传入的Elixir本地函数AST

问题背景

我希望获取作为参数传入的本地函数的AST,例如在doSomething(fn a -> a * 10 end)这类调用中拿到传入匿名函数的抽象语法树。

我最初做了如下尝试:

def test do
  inspectAST(fn a,b -> a + b + 42 end)
  inspectAST(quote do: fn a,b -> a + b + 42 end)
  inspectFunctionInfo(fn a,b -> a + b + 42 end)
  :ok
end

def inspectAST(this_is_AST) do
  IO.inspect(this_is_AST)
  IO.inspect("--------------------------------")
end

def inspectFunctionInfo(fun) do
  IO.inspect(Function.info(fun))
  IO.inspect("--------------------------------")
end

运行后得到的输出如下:

iex(3)> Utils.test
#Function<0.30424669/2 in Utils.test/0>
"--------------------------------"
{:fn, [],
 [
   {:->, [],
    [
      [{:a, [], Utils}, {:b, [], Utils}],
      {:+, [context: Utils, import: Kernel],
       [
         {:+, [context: Utils, import: Kernel],
          [{:a, [], Utils}, {:b, [], Utils}]},
         42
       ]}
    ]}
 ]}
"--------------------------------"
[
  pid: #PID<0.485.0>,
  module: Utils,
  new_index: 1,
  new_uniq: <<58, 7, 203, 172, 99, 108, 54, 80, 24, 151, 75, 56, 73, 174, 138,
    177>>,
  index: 1,
  uniq: 30424669,
  name: :"-test/0-fun-1-",
  arity: 2,
  env: [],
  type: :local
]
"--------------------------------"

目前通过inspectAST(quote do: fn a,b -> a + b + 42 end)的写法可以拿到目标AST,但我希望直接用inspectAST(fn a,b -> a + b + 42 end)的形式传参,不需要额外加quote do:包裹。


实现方案

Elixir 代码在执行前会先完成编译,直接传入的fn ... end在运行时已经是编译完成的函数闭包,不再保留原始AST结构,常规运行时逻辑无法反向从闭包还原出原始AST。要实现无quote直接传函数拿AST,需要通过宏在编译期截获传入的表达式,具体实现如下:

defmodule Utils do
  defmacro inspectAST(fun_expr) do
    quote bind_quoted: [ast: Macro.escape(fun_expr)] do
      IO.inspect(ast, label: "传入函数的AST")
      IO.inspect("--------------------------------")
      # 如果需要同时拿到可执行的函数本身,可以直接编译表达式
      fun = unquote(fun_expr)
      IO.inspect(fun.(1, 2), label: "函数执行测试(传入1,2)")
      IO.inspect("--------------------------------")
      fun
    end
  end

  def test do
    # 直接传匿名函数即可,不需要额外quote包裹
    inspectAST(fn a,b -> a + b + 42 end)
    :ok
  end
end

运行Utils.test/0的输出:

传入函数的AST: {:fn, [],
 [
   {:->, [],
    [
      [{:a, [], Utils}, {:b, [], Utils}],
      {:+, [context: Utils, import: Kernel],
       [
         {:+, [context: Utils, import: Kernel],
          [{:a, [], Utils}, {:b, [], Utils}]},
         42
       ]}
    ]}
 ]}
"--------------------------------"
函数执行测试(传入1,2): 45
"--------------------------------"

注意事项

  • 这个方案的本质是把原本在运行时执行的逻辑提前到编译期,inspectAST是宏不是普通函数,它接收到的fun_expr参数就是调用位置传入的原始AST,不需要手动写quote包裹
  • 如果传入的是已经绑定到变量的函数(比如先写f = fn a,b -> a+b+42 end再调用inspectAST(f)),拿到的AST就是变量本身的引用{:f, [], __MODULE__},拿不到函数内部结构——这是编译逻辑决定的,没有办法绕过,因为变量绑定的函数来源可能是动态传入的,编译期无法确定它的结构。

内容的提问来源于stack exchange,提问作者Abyssian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 00:39:35