如何无需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
相关产品推荐
相关产品推荐

