Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variable Scope issue in Macro

Tags:

I have a macro works if defined in main.

macro check_set(d,v)
    nm = string(v)
    quote
        global $v
        if haskey($d,$nm)
            $v = $d[$nm]
        end
    end
end

However, when I put it inside a Module (macro defined inside the module) to use in a function (also defined inside the module), I get scope issues.

export setVars

function setVars(val::Dict)
   global max_iter
   @check_set val max_iter
end

In main I call setVars(config) where config is a dictionary as expected. I get:

ERROR: UndefVarError: val not defined

Adding @macroexpand I see:

begin
    #= /home/dpazzula/Documents/Stuff/src/Stuff.jl:156 =#
    global max_iter
    #= /home/dpazzula/Documents/Stuff/src/Stuff.jl:157 =#
    if Stuff.haskey(Stuff.val, "max_iter")
        #= /home/dpazzula/Documents/Stuff/src/Stuff.jl:158 =#
        Stuff.max_iter = Stuff.val["max_iter"]
    end
end

So the ERROR makes sense, it is looking for Stuff.val when val is locally scoped to the function.

How do I get it to look for the locally scoped val?

like image 805
DomPazz Avatar asked Mar 18 '21 18:03

DomPazz


1 Answers

Your issue is related to macro hygiene. In your case, since you want d and v to refer to variable names in the scope of the macro call environment, you must "escape" them with esc

Here would be a minimal working example along the lines of what you wrote:

module Stuff

macro check_set(d,v)
    nm = string(v)
    d = esc(d)
    v = esc(v)
    quote
        global $v
        if haskey($d,$nm)
            $v = $d[$nm]
        end
    end
end

end #module Stuff

We can check that this expands to what is expected:

julia> @macroexpand Stuff.@check_set val max_iter
quote
    #= REPL[1]:8 =#
    global max_iter
    #= REPL[1]:9 =#
    if Main.Stuff.haskey(val, "max_iter")
        #= REPL[1]:10 =#
        max_iter = val["max_iter"]
    end
end

And that it also behaves as expected at run time:

julia> function setVars(val::Dict)
           Stuff.@check_set val max_iter
       end
setVars (generic function with 1 method)

julia> config = Dict("max_iter" => 42)
Dict{String,Int64} with 1 entry:
  "max_iter" => 42

julia> setVars(config)
42

julia> max_iter
42
like image 191
François Févotte Avatar answered Sep 30 '22 18:09

François Févotte