Scope of dictionary-generated variables

Saying I’d like to create variables from a dictionary.
With the following codes, I found the former generated variables aren’t deliminated after each run of the function.

D1 = Dict("a" => 1, "b" => 2)
D2 = Dict("a" => 3, "b" => 4, "c" => 5)

function key_var(dict)
    for key in keys(dict)
        s = Symbol(key)
        @eval $s = $(dict[key])
    end
    println("a=$a, b=$b")
    println(@isdefined c)
end

key_var(D1)
key_var(D2)
key_var(D1)

The results on my machine with julia-1.6-beta1 in both REPL and julia script.jl are

a=1, b=2
false
a=3, b=4
true
a=1, b=2
true

Is there is a way to manually free all these variables?
var=nothing isn’t an option, since a Nothing variable still leads

a = nothing
@isdefined a
true

You can’t create local variables in this way: Any variables you create via eval are always going to be global because eval itself always works in global scope. As far as I know, there’s also no way to un-bind a global variable (and I don’t think there’s any need or benefit to doing so anyway).

Maybe a better question would be: what is the actual problem you are trying to solve? There may be a better solution that doesn’t require dynamically generating variables like this.

2 Likes

Thanks for the answer.
I’m working a numerical PDE solver Kinetic.jl, where currently the setups can be read from config file into a dictionary. Certainly there can be alternatives to do this, and I didn’t realized the property of eval before.

Why don’t you just use Julia code to define the setups directly?

I have both.
For me, keeping the way of dictionary as configure has the benefits.
Different cases can share one main function where different variables are generated from dict and go into different workflows, and this way is friendly to me for using remote clusters.
I’m not writing the best Julian codes, so there’re very likely better options?

Why do you need to generate variables? You can just extract the variables from the dictionary, i.e. use D1[:a] instead of a when you are generating your models.

By the way, it is more common to use symbols instead of strings as dictionary keys:

D1 = Dict(:a => 1, :b => 2)

I set it this way:
For different cases, the dictionary has different keys. So after the generation of the dict, I don’t know the exact variables there beforehand. And I choose some exclusive variables with @isdefined to choose the corresponding next functions.
It works in real simulaiton (since there’s only one input), but in the test kind of messes up things.
Maybe it’s a silly way… :lying_face:
And thanks for the symbol keys usage :slight_smile:

Is there a limited number of names you can use from the dictionary?
Because that sounds like a good case for dict unpacking into keyword arguments

foo(; x = nothing, y = nothing, kwargs...) = _foo(x, y) # ignore all possible keys except `x` and `y`

_foo(::Nothing, ::Nothing) = error("No arguments provided")
_foo(x, ::Nothing) = 2x
_foo(::Nothing, y) = 2y
_foo(x, y) = x + y

d = Dict(:x => 1)
foo(;d...)
1 Like

This sounds suspiciously like what types and multiple dispatch already do.

If you replace “dictionaries with different sets of keys” with “different struct types”, then suddenly this goes from being a very difficult problem to being just any other Julia code using multiple dispatch. Can you just use the type system for this problem?

1 Like

Yes I agree.
Currently there’s no struct defined in this preprocessing step, and the work flows are based on dictionary and if,else.
It’s feasible to turn it into structs and multiple dispatch.

Cool stuff! That’s something I didn’t know before and I think it’s pretty useful.
Thanks for the input.