Metaprogramming & variable scope

I have some function as such:

function somefunction(df::AbstractDataFrame, indexcols::Array{Symbol,1})
...
    dummydf = similar(df,0)
    for col in indexcols
        dummydf[col] = []
        @eval $col = unique(df[$(QuoteNode(col))])
    end

...
    return dummydf
end

For the @eval statement in the for loop, it’s telling me that df is not defined. I’m guessing that it’s looking for df within the scope of the eval statement. I tried wrapping the df in QuoteNode, but that didn’t work either. What am I doing wrong here?

And as an extension to this… How does one get better at metaprogramming? I don’t come from a Clojure or LISP background, but I understand it and how it’s powerful from a conceptual perspective. Reading the docs has been… only somewhat useful, I think I’m at a much lower level than it’s expecting me to be.

This is less about metaprogramming in general and more about @eval (and eval()) in particular: eval always happens at global scope. This means that it will both try to create the variable $col in global scope and that it will do so by trying to read a variable named df at global scope. Neither of those are what you want.

In fact, @eval is not really going to help you in this particular case. More importantly, what you’re trying to do, as written, isn’t possible. You’re trying to create local variables where the number and names of those variables depends on the values inside indexcols. But there’s no way to do that in Julia: the existence of local variables inside a function cannot depend on the values of the inputs to that function.

This seems like a case where metaprogramming is not particularly helpful. Instead, why not just construct a Dict{Symbol, Whatever} and fill that dict?

4 Likes

This was very insightful, and clears up a number of questions I had surrounding eval. After thinking on your suggestion of using Dict{Symbol, <something>}, I ended up with a working solution just using Base.Iterators. Thank you so much for your help!