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?
Thanks Arda for the thorough response! This helped, but part of me thinks there may be a better way to do this as it seems too roundabout. Since I am writing a genetic program, I will need to create 500 of these ASTs, all with random functions and terminals from a set of allowed functions and terminals (fs and ts in the code). I will also need to test each function with 20 different values of x and v.
In order to accomplish this with the information you have given, I have come up with the following macro:
I can then supply a list of 500 random function names in my main() function, such as ["func1, func2, func3,.....". Which I can eval with any x and v values in my main function. This has solved my issue, however, this seems to be a very roundabout way of doing this, and may make it difficult to evolve each AST with each iteration.
In the Metaprogramming part of the Julia documentation, there is a sentence under the
eval()
and effects section which saysSimilarly, the REPL help
?eval
will give you, on Julia 0.6.2, the following help:I assume, you are working in the
Main
module in your example. That's why you need to have the globals defined there. For your problem, you can usemacro
s and interpolate the values ofx
andy
directly inside the macro.A minimal working example would be:
Here,
@eval_line
macro does the following:As you can see, the values of
macro
's arguments are interpolated inside the macro and the expression is given to the user accordingly. When the user does not behave,a user-friendly warning message is provided to the user at parse-time, and a
DomainError
is thrown at run-time.Of course, you can do these things within your functions, again by interpolating the variables --- you do not need to use
macro
s. However, what you would like to achieve in the end is to combineeval
with the output of a function that returnsExpr
. This is what themacro
functionality is for. Finally, you would simply call yourmacro
s with an@
sign preceding themacro
name:EDIT 1. You can take this one step further, and create functions accordingly:
Then, you can call this macro to create different line definitions in the form of functions to be called later on:
EDIT 2. You can, I guess, achieve what you would like to achieve by using a macro similar to that below:
which will give the following, for instance:
Here is an example for generating an (anonymous) function. The result of eval can be called as a function and your variable can be passed as parameters:
The
dump
function is very useful to inspect the expression that the parsers has created. For two input arguments you would use a tuple for example asargs[1]
:Does this help?