The question relates to Symbols and metaprogramming, been through the docs many times but still getting my head around it.
I’ve been struggling to pinpoint a bug in my code for the last 2 days and at last I’ve reduced it to this minimal example, can anyone help me understand what’s going on please? (In case it matters, Julia v0.6.3 on Windows 7.)
This code runs fine:
foo = :foo
@eval ($println)($foo)
> foo
and this too:
foo = :foo
function maintest()
@eval ($println)($foo)
end
maintest()
> foo
But when I define foo in the function:
function maintest()
foo = :foo
@eval ($println)($foo)
end
maintest()
I get:
ERROR: LoadError: UndefVarError: foo not defined
As a sanity check I did the following, which works as expected:
function maintest()
foo = "bar"
@eval ($println)($foo)
end
maintest()
> bar
So my question is, why do I get the error when defining the symbol in the function’s hard local scope?
Eval always happens in the global scope. This makes sense because functions are essentially statically compiled so they don’t really have dynamic behavior (giving the speed). If course, if you want to do metaprogramming inside of a function, just metaprogram the AST (and return expressions) or use an @generated function to build function expressions. Or, just pull in the global with global x in the function if that’s what you want to do.
The difference between Symbol and String in maintest occurs because: here
function maintest()
foo = :foo
@eval ($println)($foo)
end
the value of $foo that gets interpolated is :foo, so @eval is calling println(foo) (since :(println($(:foo)))), but then foo does not exist in that context. The foo that is now being called for is a different foo (note that the other one is already interpolated).
While here,
function maintest()
foo = "bar"
@eval ($println)($foo)
end
the value of $foo that is interpolated is "bar", so the commaned given to @eval is println("bar"), which does not require the lookup of an undefined variable, so that is okay and does not give an error.
Thanks for your explanation that makes sense. I’ve gone over the @generated and AST docs several times to try and understand your suggestions, I was wondering if you might be able to give a small code example to demonstrate? Essentially, in (Julia-esque) pseudocode, what I’m trying to achieve is this:
function (self::ImmutableStruct, fieldsToModify::Array{Symbol,1})
for i in fieldsToModify
i = modifyingFunction( getfield(self, i) )
end
createNewImmutableStructWithModifiedFields(...)
end
Also the fields not included fieldsToModify should be duplicated from original struct.
Edit: from my understanding of @generated now it doesn’t seem like it would solve this particular problem
@generated is not what you want you. You want to write a macro @modifier this_struct :these :fields that takes in those args and returns an expression for building the struct.
Thanks, I’m trying with a macro now. Sorry just a follow up to your first answer the bit I’m struggling to understand, if eval happens in the global scope how come this works ok?
function maintest()
foo = "bar"
@eval ($println)($foo)
end
maintest()
> bar
ah ok I get it thanks! But what if I want something to be interpolated to a symbol, i.e. so I can use the ‘getfield’ function in an expression? I tried playing with ‘esc’ but that doesn’t seem to do the trick.
Going back to a function for the sake of example, i think interpolating to a symbol is the crux of my problems, the rest I seem to have got working:
struct testhang
a::Float64
b::Float64
c::Float64
end
function maintest(structy, symbolist)
for (i, v) in enumerate(symbolist)
@eval $v = ($getfield)($structy, $(esc(v)))
end
end
insty = testhang(1.0, 2.0, 3.0)
maintest(insty, [:a, :b, :c])
edit: in other words, in the local namespace I want variables ‘a’, ‘b’ and ‘c’ to be set equal to ‘structy.a’, ‘structy.b’ and ‘structy.c’ respectively.
edit: woops sorry I know I’m missing the @eval, just put it in
@eval evaluates in global scope so you cannot use it to declare local variables.
You can use something like
julia> using Parameters
julia> struct Foo
a
b
c
end
julia> function f(f::Foo)
@unpack a, b, c = f
print(a, b, c)
end
f (generic function with 1 method)
julia> f(Foo(1, 2, 3))
123
You would use a macro for your usecase but note that you can only construct it so that at expansion time the macro knows exactly what syntax to generate. So you need to fix the type or the fields to unpack before runtime.
OK thanks, I see that now. And after some playing around I figured why the getfield wasn’t working. Will post code in case others read this thread and want to know. Realised that if you want to interpolate to the symbol value you have to wrap it in a $(Expr(:quote, symvar)).
So the code below basically does what I need as I don’t really mind that the variable is created in global scope.
struct foo
a::Float64
b::Float64
end
function modifyfoo(self::foo, tomodify::Array{Symbol,1})
for symvar in tomodify
@eval $symvar = ($getfield)($self, $(Expr(:quote, symvar)))
end
end
instance = foo(0.0, 0.0)
modifyfoo(instance, [:a, :b])
println("From global\na: $a \nb: $b")
> From global
> a: 0.0
> b: 0.0