Not able to capture variable (via symbol) from outer scope?

I have the following minimal example:

function foo()
    myvar1 = 55
    function bar()
        println(myvar1) #<- OK
        s = Symbol("myvar", 1)
        println(@eval $s) # <-ERROR: LoadError: UndefVarError: myvar1 not defined
    end
    bar()
end
foo()
myvar2 = 77
s = Symbol("myvar", 2)
println(@eval $s) #<-- OK

Why can’t I access myvar1 by evaluating the proper symbol?

What do you mean? the variable myvar1 hasn’t been defined yet when you call the function. what you are doing is equivelent to typing

mynewvar2134

into the REPL. You will get an error that it is undefined.

@ pdeffebach But I did define myvar1 on row 2, before calling bar().

@eval and eval execute in global scope. myvar1 is defined in a local scope.

2 Likes

I see. Is there anyway to accomplishing what I want: I want to be able to pass a digit (“1” in the case above) to the function bar, use that digit “X” to capture the variable myvarX in foo(), and use it to compute stuff within bar()?

(I realize I can just send in the variable itself, but I’m trying to understand the Julia language.)

It is not possible using only functions. It seems that you are trying to work with metaprogramming. You can define a macro that receives an input and returns code:

macro append_myvar(x)
	esc(Symbol(:myvar, x))
end
function foo()
    myvar1 = 55
    function bar()
        println(myvar1) #<- OK
        s = @append_myvar 1
        println(s)
    end
    bar()
end

Note that you shouldn’t use metaprogramming for this. It is just an example. In your case, an Array or Dict storing [myvar1, myvar2...] is more appropriate.

2 Likes

No you can’t. Do not try to construct variable names at runtime. It is also NOT possible with macros. @lucas711642’s post does not do anything different from writing myvar1 directly.

2 Likes

Yes, the code I provided only works with the 1 hard-coded in the function. You cannot pass the argument x to the macro at run time.

OP consider using a dictionary to store values instead of making variables with names generated at run time.

2 Likes

Yes, this seems to fall into category 3 of my list of common mis-applications of metaprogramming. See also my 2019 JuliaCon talk on why you mostly shouldn’t use metaprogramming.

2 Likes

IMHO, using metaprogramming to create variables systematically can be quite handy in some cases. In my work, for example, I have a composite type (say MyType) that has a bunch of fields (say field1, field2, field3…). An instance of this type is passed to a function like the following:

function foo(myarg::MyType, ...)
	field1 = myarg.field1; field2 = myarg.field2 ...
	...
end

wait, these statements on the first line seem tedious. So I use a macro @fields2variables which in turn receives:

function foo(myarg::MyType, ...)
	@fields2variables MyType myarg
	...
end

and, as long as MyType is defined, @fields2variables gets its fieldnames and transform the code into:

function foo(myarg::MyType, ...)
	local field1, field2 ... = myarg.field1, myarg.field2, ...
	...
end

where the local keyword prevents us from incorrectly modifying variables in the enclosing scope.

Another common example in my code is something like:

@inbounds for k = eachindex(a, b, ...)
	ak = a[k]; bk = b[k]; ...
	...
end

whose first line can also be processed by a macro in a similar way.

I think macros like those are very convenient when writing internal code, but they probably shouldn’t be part of a public API, because they can hide some bugs (although the local keyword usually helps preventing bugs in the aforementioned examples), so you have to be careful when using them. Of course, the validity this kind of usage is just my opinion.

Again, this does not seem to be the case of the OP’s question, where an Array or Dict seems to be more appropriate.