【问题标题】:Julia-Lang Metaprogramming: turn expression into function with expression-dependent argumentsJulia-Lang 元编程:将表达式转换为具有表达式相关参数的函数
【发布时间】:2015-06-28 23:56:08
【问题描述】:

给定一个值字典,

值 = {:A => 3, :B => 1}

将(任意)表达式转成类似

expr = :(2*A)

到一个评估表达式的函数 foo(values) 中,因此在这种情况下 foo(values) = 6。生成的函数将被调用数百万次,因此速度是一个重要的考虑因素。如有必要,我很乐意采用稍微不同的方法,只要它可以自动化。

我尝试过的事情:

  1. 使用 convert(Function, expr) 进行转换,建议 here。对我来说失败了(Julia 0.3.8-pre):

    convert 没有匹配 convert(::Type{Function}, ::Expr) 的方法

  2. 使用@eval 可以做到

    @eval foo(A) = $(expr)

    然后调用 foo(values[:A]),但这需要知道 expr 依赖于 A(并且仅依赖于 A)。

  3. 我编写了一个函数 find_vars(exp) 来返回 expr 中的符号(在本例中为 [:A]),但在 @eval 中找不到如何使用它们方法。

【问题讨论】:

  • 我相信您链接的帖子是特定于 SymPy.jl 包的
  • @ptb:感谢您指出这一点!我在 SymPy 包中挖掘了转换函数。在这种情况下它没有帮助,但请参阅line 113 in type.jl

标签: dictionary expression metaprogramming julia


【解决方案1】:

感谢@ptb 和另一个metaprogramming question 的解决方案,我找到了一个更简单但更慢的解决方案:

function foo(values, expr)
    expr =  quote
                A = values[:A]
                B = values[:B]
                return $(expr)
            end
    eval(expr)
end        

从字典中读取值也可以通过将内部评估替换为以编程方式完成

    $([:($k = $v) for (k, v) in values]...) 
    return $(expr)

【讨论】:

  • 这些函数非常不同,只有在程序执行期间valuesexpr 从不改变时才会产生相同的结果。我怀疑没有人做你认为他们做的事情,因为你已经定义了你的函数来接受多余的参数(即 foo 应该只接受 values 并且 fooauto 应该是一个零参数函数。包裹在 $ 中的项目是在运行 @eval 时进行评估,而在调用函数时 not。尝试使用 macroexpand(:(@eval ... end)) 包装您的 @eval 块以查看实际生成的函数。
  • 你完全正确!我必须添加一个外部函数调用来在运行时编译内部函数。当然,这会增加很多开销,所以现在只能进行 10.000 次调用/秒。对于我的代码,没关系:我将在表达式中放入一个昂贵的 for 循环。
  • @ptb:我分析了您的代码并获得了大约 10.000.000 次调用/秒 - 太棒了!因此,对于给定的问题陈述,您的方法要好得多!
【解决方案2】:

Base.Cartesian 有一个未导出的函数lreplace,这可能是您想要的。然后您可以执行以下操作:

julia> values = Dict(:A=>3, :B=>1)
Dict{Symbol,Int64} with 2 entries:
  :B => 1
  :A => 3

julia> import Base.Cartesian.lreplace

julia> expr = :(2*A)
:(2A)

julia> function lreplace_all(expr, d)
       for (k, v) in d
           expr = lreplace(expr, k, v)
       end
       expr
       end
lreplace_all (generic function with 1 method)

julia> lreplace_all(expr, values)
:(2 * 3)

julia> @eval foo(A) = $(lreplace_all(:(2A), values))
foo (generic function with 1 method)

julia> foo(1)
6

虽然,由于 A 是由 values 字典定义的,因此将 foo 定义为零参数函数更有意义(除非我遗漏了什么)。

编辑: 重新阅读您的问题后,您似乎想将实际字典传递给函数,而不是像我上面所做的那样在编译时提供可用的值。在这种情况下,我们有一点创意:

首先,我们需要一个类似lreplace 的函数,它可以处理表达式,这很简单

julia> dictreplace!(ex, s, v) = ex
dictreplace! (generic function with 1 method)

julia> dictreplace!(ex::Symbol, s, v) = s == ex ? v : ex
dictreplace! (generic function with 2 methods)

julia> function dictreplace!(ex::Expr, s, v)
           for i=1:length(ex.args)
               ex.args[i] = dictreplace!(ex.args[i], s, v)
           end
       ex
       end
dictreplace! (generic function with 3 methods)

julia> dictreplace(ex, s, v) = dictreplace!(copy(ex), s, v)
dictreplace (generic function with 1 method)

现在我们想用字典查找替换字典键中出现的每个符号

julia> function dictreplace_all(expr, kys, dsym)
           for k in kys
               expr = dictreplace(expr, k, :($(dsym)[$(QuoteNode(k))]))
           end
       expr
       end
dictreplace_all (generic function with 1 method)

julia> dictreplace_all(:(2A), keys(values), :d)
:(2 * d[:A])

julia> @eval foo(args) = $(dictreplace_all(:(2A), keys(values), :args))
foo (generic function with 1 method)

julia> values[:A] = -99
-99

julia> foo(values)
-198

【讨论】:

  • 这个解决方案很棒!但是我有一个类似的问题,它使用浮点数而不是整数并且lreplace 不支持浮点数。你对如何解决它有任何想法吗?谢谢
  • 奇怪的是 lreplace 不处理浮点数,但上面的 dictreplace! 方法没有使用 lreplace 所以它们应该可以正常工作
猜你喜欢
  • 1970-01-01
  • 2019-03-03
  • 2021-03-28
  • 2021-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-12
相关资源
最近更新 更多