Evaluate expression with local variables

时间:2018-03-09 19:00:09

标签: julia metaprogramming

I'm writing a genetic program in order to test the fitness of randomly generated expressions. Shown here is the function to generate the expression as well a the main function. DIV and GT are defined elsewhere in the code:

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()

I am generating a random expression based on certain allowed functions and variables. As seen in the code, the expression can only have symbols x and v, as well as the value -1. I will need to test the expression with a variety of x and v values; here I am just using x=1 and v=1 to test the code.

The expression is being returned correctly, however, eval() can only be used with global variables, so it will error out when run unless I declare x and v to be global (ERROR: LoadError: UndefVarError: x not defined). I would like to avoid globals if possible. Is there a better way to generate and evaluate these generated expressions with locally defined variables?

3 个答案:

答案 0 :(得分: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
 [...]

这有帮助吗?

答案 1 :(得分:1)

在Julia文档的Metaprogramming部分, eval()和效果部分下面有一句话说

  

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

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

  

评估给定模块中的表达式并返回结果。每个Module(除了用baremodule定义的那些)都有自己的{-1}}的1参数定义,它定义该模块中的表达式。

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

最小的工作示例是:

y

此处,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

parse -time向用户提供了用户友好的警告消息,并在运行时抛出Main> @macroexpand @eval_line([1,2,3], 7, 8) WARNING: [1, 2, 3] is not a real number. :((Main.throw)((Main.DomainError)()))

当然,您可以在函数中执行这些操作,再次插入变量---您需要使用DomainError s。但是,您最终想要实现的是将macro与返回eval的函数的输出结合起来。这就是Expr功能的用途。最后,您只需使用macro名称前面的macro号码呼叫@

macro

编辑1。您可以更进一步,并相应地创建功能:

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

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

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

编辑2。我猜,您可以使用类似下面的宏来实现您想要实现的目标:

@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

将提供以下内容,例如:

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

答案 2 :(得分:0)

感谢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。