In the context of an optimal control interface, I am trying to create a variable inside a Julia macro, then return the variable to the REPL, the variable should also have the name I specified from the macro input argument.
For example, I want to have a variable called y, so I use it as the input argument, it should return a variable named y
julia> @add_my_variable(my_model,y>=0)
y
and then I should be able to use the variable in other functions in the REPL without "UndefVarError" saying that y is not defined
julia> y
y
julia> set_initial_guess(y,1)
...
I tried to mimic what JuMP package does to achieve it, so I read the definition of @variable in JuMP, the function below (I excluded the irrelevant part of the function) is the key to achieving the functionality, but I find it hard to understand.
function _macro_assign_and_return(code, variable, name; model_for_registering = nothing,)
return quote
$variable = $code
# This assignment should be in the scope calling the macro
$(esc(name)) = $variable
end
end
I traced all the way to the beginning of the macro for the meaning of the input arguments: "code" is an instance of GenericVariableRef type (detailed at the bottom), "variable" is a gensym(), "name" is the symbol from the macro input expression (like :y in :(y>=0)).
First, how can we assign a structure instance to an anonymous symbol "variable"?
Second, after I implemented on my side, it always reports error at the line of the return (shown below), I assume it cannot recognize y when evaluating $(esc(name)), so I think JuMP must have somehow constructed a variable y in the macro already, using the same name as the symbol :y from the input? I do not know if and how it can be achieved.
julia> @my_variable(model,y>=0)
UndefVarError: y not defined
Stacktrace:
[1] macro expansion
The background information of the structure GenericVariableRef is shown below
struct GenericVariableRef{T} <: AbstractVariableRef
model::GenericModel{T}
index::MOI.VariableIndex
end
For more relevant info, you can visit the link below, line 2479 for the macro, line 2681 for the function call in the macro, and line 121 for the complete function definition.
https://github.com/jump-dev/JuMP.jl/blob/89e56230306b76cfb81ab4121dbe253061362776/src/macros.jl#L121
Thank you for reading this far.
This is not really a JuMP question, but a broader Julia question of metaprogramming. It might help to read https://docs.julialang.org/en/v1/manual/metaprogramming/.
Getting the "hygiene" of macros correct can be quite difficult, because there are some very subtle behaviors.
For your y
example, this might help:
julia> macro add_variable(model, x)
return esc(quote
$x = 1
end)
end
@add_variable (macro with 1 method)
julia> @add_variable(nothing, y)
1
julia> @macroexpand @add_variable(nothing, y)
quote
#= REPL[20]:3 =#
y = 1
end
julia> y
1
You need to interpolate the Symbol given by the user (:y
), and then you need to esc
the expression to tell Julia to evaluate it in the user's current scope.
I don't know if the JuMP code is a good place to look at when learning. It's overly complicated, and we want to rewrite it to simplify it at some point...