Julia 用局部变量计算表达式

Julia 用局部变量计算表达式,julia,metaprogramming,Julia,Metaprogramming,我正在写一个遗传程序,以测试随机生成的表达式的适用性。这里显示的是生成表达式的函数以及主函数。DIV和GT在代码的其他地方定义: function create_single_full_tree(depth, fs, ts)

我正在写一个遗传程序,以测试随机生成的表达式的适用性。这里显示的是生成表达式的函数以及主函数。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:UndevarError:x未定义)。如果可能的话,我会尽量避免使用全球化。有没有更好的方法来生成和计算这些使用本地定义变量生成的表达式?

以下是生成(匿名)函数的示例。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
 [...]
这有帮助吗?

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

每个
模块
都有自己的
eval()
函数,用于在其全局范围内计算表达式

类似地,REPL帮助
?eval
将在Julia 0.6.2上为您提供以下帮助:

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

我假设您正在示例中的
Main
模块中工作。这就是为什么需要在那里定义globals。对于您的问题,您可以使用
s并直接在宏内插入
x
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)
如您所见,
的参数值在宏中插入,表达式相应地提供给用户。当用户没有行为时

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

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

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

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

也许可以使用输入参数创建函数的AST(而不是表达式的AST)?谢谢,请看下一篇博文。我没有说你应该定义500个不同的函数。我只是告诉你,如果你愿意,你可以用宏来定义函数(或者,具有相同原理的闭包)。请检查我修改过的答案。