4

Consider the following function:

function func(vars::Dict{Symbol, Any})

end

I want to create local variables in a func's scope, where: variable names are each the key of the vars and variable values are values in a vars corresponding to the given key.

I am aware that I could use eval() in something like:

for (k, v) in vars
    eval(:($k = $v))
end

However, that will define variables in the global scope, which has performance repercussions and might override existing global variables.

P.S. I will add a context in case you have suggestions that are drastically different than the approach I am going for. I am implementing a wrapper around the JuMP allowing the users to dynamically create optimization models with arbitrary constraint expressions. Since the constraint expressions are defined using symbols, and JuMP requires those symbols to be defined in the current scope, I want the user to provide those symbols and their values in a dictionary to the function. Defining the symbols globally would be cumbersome as the user should ideally be able to run the function multiple times with the same constraint expressions (i.e. same symbols) but different values.

aberdysh
  • 1,634
  • 2
  • 13
  • 34

1 Answers1

2

Note: I'm not a metaprogramming expert!

I think you could do something like this, not with generic functions, but with either a macro or a generated function instead.

Based on this:

julia> macro bar(dict)
           dump(dict, 10)
       end

julia> @bar Dict(:foo => "foo", :bar => "bar", :baz => "baz");
Expr 
  head: Symbol call
  args: Array(Any,(4,))
    1: Symbol Dict
    2: Expr 
      head: Symbol =>
      args: Array(Any,(2,))
        1: Expr 
          head: Symbol quote
          args: Array(Any,(1,))
            1: Symbol foo
          typ: Any
        2: ASCIIString "foo"
      typ: Any
    3: Expr 
      head: Symbol =>
      args: Array(Any,(2,))
        1: Expr 
          head: Symbol quote
          args: Array(Any,(1,))
            1: Symbol bar
          typ: Any
        2: ASCIIString "bar"
      typ: Any
    4: Expr
      head: Symbol =>
      args: Array(Any,(2,))
        1: Expr
          head: Symbol quote
          args: Array(Any,(1,))
            1: Symbol baz
          typ: Any
        2: ASCIIString "baz"
      typ: Any
  typ: Any

I was able to do something like this, it could do more error checking and also generic macros would make this easier (at least for error checking) in the development branch:

julia> macro foo(dict)
           blk = Expr(:block)
           if dict.head == :call && dict.args[1] == :Dict
               for arg in dict.args[2:end]
                   sym = arg.args[1].args[1]
                   val = arg.args[2]
                   push!(blk.args, esc(:($sym = $val)))
               end
           else
               error("Need a Dict{Symbol, Any}")
           end
           blk
       end

julia> @foo Dict(:foo => "foo", :bar => "bar", :baz => "baz");

julia> @show foo bar baz;
foo = "foo"
bar = "bar"
baz = "baz"

julia> let    # local scope
           @foo Dict(:foo => "foo", :bar => "bar", :baz => "baz")
           @show foo bar baz
       end;
foo = "foo"
bar = "bar"
baz = "baz"

Notice you can't create a dict varible and then pass it to the @foo macro (again generic macros could make this easier as there could be a method for Symbol also and dispatch an error?):

julia> dict = Dict(:foo => "foo", :bar => "bar", :baz => "baz");

julia> @foo dict
ERROR: type Symbol has no field head

At this point something like:

@foo a 1 b 2 c 3

or

@foo :a=>1 :b=>2 :c=>3

Would be better IMHO.

I would appreciate a lot if someone explains me why this is not optimal! This answer should've been a comment, but it's lots of code.

I'll try to implement something similar with generic macros and generated functions and update the post.

HarmonicaMuse
  • 7,633
  • 37
  • 52