【问题标题】:Evaluate expression with local variables使用局部变量评估表达式
【发布时间】:2018-03-09 19:00:09
【问题描述】:

我正在编写一个基因程序来测试随机生成的表达式的适应度。这里显示的是生成表达式的函数以及主函数。 DIV 和 GT 在代码的其他地方定义:

function create_single_full_tree(depth, fs, ts)                                                                                                                                         
"""                                                                                                                                                                                 
Creates a single AST with full depth                                                                                                                                                
Inputs                                                                                                                                                                              
    depth   Current depth of tree. Initially called from main() with max depth                                                                                                      
    fs      Function Set - Array of allowed functions                                                                                                                               
    ts      Terminal Set - Array of allowed terminal values                                                                                                                         
Output                                                                                                                                                                              
    Full AST of typeof()==Expr                                                                                                                                                      
"""                                                                                                                                                                                 

# If we are at the bottom                                                                                                                                                           
if depth == 1                                                                                                                                                                       
    # End of tree, return function with two terminal nodes                                                                                                                          
    return Expr(:call, fs[rand(1:length(fs))], ts[rand(1:length(ts))], ts[rand(1:length(ts))])                                                                                      
else                                                                                                                                                                                
    # Not end of expression, recurively go back through and create functions for each new node                                                                                      
    return Expr(:call, fs[rand(1:length(fs))], create_single_full_tree(depth-1, fs, ts), create_single_full_tree(depth-1, fs, ts))                                                  
end                                                                                                                                                                                 
end                                                                                                                                                                                     

function main()                                                                                                                                                                         
    """                                                                                                                                                                                 
    Main function                                                                                                                                                                       
    """                                                                                                                                                                                 

    # Define functional and terminal sets                                                                                                                                               
    fs = [:+, :-, :DIV, :GT]                                                                                                                                                            
    ts = [:x, :v, -1]                                                                                                                                                                   
    # Create the tree                                                                                                                                                                   
    ast = create_single_full_tree(4, fs, ts)                                                                                                                                            
    #println(typeof(ast))                                                                                                                                                               
    #println(ast)                                                                                                                                                                       
    #println(dump(ast))                                                                                                                                                                                                                                                                                 
    x = 1                                                                                                                                                                               
    v = 1                                                                                                                                                                               
    eval(ast)  # Error out unless x and v are globals                                                                                                                                                                  
end                                                                                                                                                                                     
main()

我正在根据某些允许的函数和变量生成随机表达式。从代码中可以看出,表达式只能有符号 x 和 v,以及值 -1。我将需要使用各种 x 和 v 值来测试表达式;这里我只是使用 x=1 和 v=1 来测试代码。

表达式被正确返回,但是,eval() 只能与全局变量一起使用,因此除非我将 x 和 v 声明为全局变量,否则它将在运行时出错(错误:LoadError:UndefVarError:x 未定义) .如果可能的话,我想避免使用全局变量。有没有更好的方法来使用本地定义的变量生成和评估这些生成的表达式?

【问题讨论】:

  • 也许可以使用输入参数创建函数的 AST(而不是表达式的 AST)?

标签: julia metaprogramming


【解决方案1】:

这是一个生成(匿名)函数的示例。 eval 的结果可以作为函数调用,你的变量可以作为参数传递:

myfun = eval(Expr(:->,:x,  Expr(:block, Expr(:call,:*,3,:x) )))
myfun(14)
# returns 42

dump 函数对于检查解析器创建的表达式非常有用。对于两个输入参数,您可以使用元组,例如 args[1]:

  julia> dump(parse("(x,y) -> 3x + y"))
  Expr
    head: Symbol ->
    args: Array{Any}((2,))
      1: Expr
        head: Symbol tuple
        args: Array{Any}((2,))
          1: Symbol x
          2: Symbol y
        typ: Any
      2: Expr
 [...]

这有帮助吗?

【讨论】:

    【解决方案2】:

    在 Julia 文档的 Metaprogramming 部分中,eval() and effects 部分下有一句话说

    每个module 都有自己的eval() 函数,用于评估其全局范围内的表达式。

    同样,REPL 帮助 ?eval 将在 Julia 0.6.2 上为您提供以下帮助:

    计算给定模块中的表达式并返回结果。每个Module(用baremodule 定义的除外)都有自己的eval 1 参数定义,用于计算该模块中的表达式。

    我假设,在您的示例中,您正在使用 Main 模块。这就是为什么您需要在那里定义全局变量的原因。对于您的问题,您可以使用macros 并直接在宏内插入xy 的值。

    一个最小的工作示例是:

    macro eval_line(a, b, x)
      isa(a, Real) || (warn("$a is not a real number."); return :(throw(DomainError())))
      isa(b, Real) || (warn("$b is not a real number."); return :(throw(DomainError())))
      return :($a * $x + $b) # interpolate the variables
    end
    

    在这里,@eval_line 宏执行以下操作:

    Main> @macroexpand @eval_line(5, 6, 2)
    :(5 * 2 + 6)
    

    如您所见,macro 的参数值被插入到宏内部,并相应地将表达式提供给用户。当用户不行为时,

    Main> @macroexpand @eval_line([1,2,3], 7, 8)
    WARNING: [1, 2, 3] is not a real number.
    :((Main.throw)((Main.DomainError)()))
    

    解析时向用户提供用户友好的警告消息,并在运行时抛出DomainError

    当然,你可以在你的函数中做这些事情,同样通过插入变量——你确实不需要需要使用macros。但是,您最终想要实现的是将eval 与返回Expr 的函数的输出结合起来。这就是macro 功能的用途。最后,您只需在 macro 名称前加上 @ 符号即可调用您的 macros:

    Main> @eval_line(5, 6, 2)
    16
    Main> @eval_line([1,2,3], 7, 8)
    WARNING: [1, 2, 3] is not a real number.
    ERROR: DomainError:
    Stacktrace:
     [1] eval(::Module, ::Any) at ./boot.jl:235
    

    编辑 1. 您可以更进一步,并相应地创建函数:

    macro define_lines(linedefs)
      for (name, a, b) in eval(linedefs)
        ex = quote
          function $(Symbol(name))(x) # interpolate name
            return $a * x + $b # interpolate a and b here
          end
        end
        eval(ex) # evaluate the function definition expression in the module
      end
    end
    

    然后,您可以调用此宏以稍后调用的函数的形式创建不同的行定义:

    @define_lines([
      ("identity_line", 1, 0);
      ("null_line", 0, 0);
      ("unit_shift", 0, 1)
    ])
    
    identity_line(5) # returns 5
    null_line(5) # returns 0
    unit_shift(5) # returns 1
    

    编辑 2. 我猜你可以通过使用类似于下面的宏来实现你想要实现的目标:

    macro random_oper(depth, fs, ts)
      operations = eval(fs)
      oper = operations[rand(1:length(operations))]
      terminals = eval(ts)
      ts = terminals[rand(1:length(terminals), 2)]
      ex = :($oper($ts...))
      for d in 2:depth
        oper = operations[rand(1:length(operations))]
        t = terminals[rand(1:length(terminals))]
        ex = :($oper($ex, $t))
      end
      return ex
    end
    

    这将给出以下内容,例如:

    Main> @macroexpand @random_oper(1, [+, -, /], [1,2,3])
    :((-)([3, 3]...))
    
    Main> @macroexpand @random_oper(2, [+, -, /], [1,2,3])
    :((+)((-)([2, 3]...), 3))
    

    【讨论】:

    • 谢谢,请看下面的下一篇文章
    【解决方案3】:

    感谢 Arda 的彻底回复!这有帮助,但我的一部分认为可能有更好的方法来做到这一点,因为它似乎太迂回了。由于我正在编写一个基因程序,我将需要创建 500 个这样的 AST,所有这些 AST 都具有来自一组允许的函数和终端(代码中的 fs 和 ts)的随机函数和终端。我还需要用 20 个不同的 x 和 v 值来测试每个函数。

    为了使用您提供的信息完成此操作,我提出了以下宏:

    macro create_function(defs)                                                                                       
            for name in eval(defs)                                                                                    
            ex = quote                                                                                                
                function $(Symbol(name))(x,v)                                                                         
                    fs = [:+, :-, :DIV, :GT]                                                                          
                    ts = [x,v,-1]                                                                                     
                    return create_single_full_tree(4, fs, ts)                                                         
                end                                                                                                   
            end                                                                                                       
            eval(ex)                                                                                                  
        end                                                                                                           
    end
    

    然后我可以在我的 main() 函数中提供一个包含 500 个随机函数名称的列表,例如 ["func1, func2, func3,....."。我可以在我的主函数中使用任何 x 和 v 值进行评估。这已经解决了我的问题,但是,这似乎是一种非常迂回的方式,并且可能难以在每次迭代中进化每个 AST。

    【讨论】:

    • 我确实没有说你应该定义 500 个不同的函数。我刚刚展示了如果您愿意,可以使用宏定义函数(或具有相同原理的闭包)。请检查我修改后的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-25
    • 2023-01-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多