首页 > 解决方案 > Julia - 在运行时创建的函数中获取运行时参数

问题描述

我试图创建一个宏来替换函数的主体,这样:

@foo function bar(a,b,c)
   *my code here*
end

而且我已经走了很远,但是我这里的代码需要知道bar(...)可以通过使用来完成的参数Base.@locals。但问题是@foo需要插入它,我认为它使用了错误的“本地范围”,或者在有参数之前进行了评估。

foo 大致如下:(expr.args[2]是函数体)

macro foo(expr::Expr)
    ...
    expr.args[2] = quote
        locals = Base.@locals
        println(locals)
        ...
    end
    
    :($expr)
end

但是通过这个调用后来创建的函数,只会打印出一个空的dict,而手动创建的函数在使用相同的代码时会打印出参数:

function test(a,b,c)
    println(Base.@locals)
end
test(1,2,3) # returns: Dict{Symbol,Any}(:a => 1,:b => 2,:c => 3)

所以我的问题是,我该如何解决这个问题,以便Base.@locals(或类似的方法)给我参数?

标签: juliametaprogramming

解决方案


我不依赖Base.@locals,而是直接从定义中获取函数参数,并执行以下操作:

using MacroTools

macro foo(expr)
    def = MacroTools.splitdef(expr)

    make_pair(arg) = :($(Meta.quot(arg)) => $arg)
    def[:body] = quote
        d = Dict($(map(make_pair, def[:args])...))
        println(d)
        $(def[:body])
    end

    MacroTools.combinedef(def)
end

关于这段代码的几点说明和解释:

  • splitdef尝试重写/修改函数定义的宏可以combinedefMacroTools. 在全球范围内,拟议的宏观具有 3 个阶段:
    1. 将函数定义拆分为单独的部分;
    2. 在函数体的开头插入你想要的代码;
    3. 将所有部分(包括修改后的主体)重新组合成一个合法的函数定义。
  • 包含函数参数的字典由以下关键成分构建:
    • make_pair接受一个给定的变量名(作为一个符号,比如说:a),并返回一个对 pair 求值的表达式:a => a
    • 然后将此make_pair函数mapped 到来自(分解的)函数定义的所有参数,以构建参数对数组;
    • 最后这个数组被放入Dict构造函数中。

我们可以检查这个示例函数定义是否扩展为预期的代码:

julia> Base.remove_linenums!(@macroexpand @foo function bar(a,b,c)
           a + b + c
       end)
:(function Main.bar(var"#26#a", var"#27#b", var"#28#c"; )
      var"#25#d" = Main.Dict(:a => var"#26#a", :b => var"#27#b", :c => var"#28#c")
      Main.println(var"#25#d")
      begin
          var"#26#a" + var"#27#b" + var"#28#c"
      end
  end)

...更一般地说,一切都在运行时按预期工作:

julia> @foo function bar(a, b, c)
           a + b + b
       end
bar (generic function with 1 method)

julia> bar(1, 2, 3)
Dict(:a => 1,:b => 2,:c => 3)
5

推荐阅读