Evaluate expression with variables from a dictionary

I have a set of variables stored in a Dict{Symbol, Any} like this:

data = Dict(
    :a=>1,
    :b=>2,
    :c=>3    # etc.
)

Then I’ve got a set of calculations that are defined as expressions with variable names that match the keys of the data set, e.g.

f1 = :(a + b)

I’m looking for a way of evaluating such expressions using the variables that are stored in data. But I prefer not to populate Main (or the module where the calculations are done) with those variables.

A macro like this might do the trick:

macro calculate(ex, dict_ex)
    dict = __module__.eval(dict_ex)
    vex = Expr[]
    for (k, v) in dict
        push!(vex, :($k = $v))
    end
    push!(vex, ex)
    return quote $(vex...) end
end
julia> @calculate a + b data
3

julia> # Or if I want to use the expression stored in f1:

julia> @eval @calculate $f1 data
3

However, this macro allocates variables for all the data contained in the dictionary, even if the expression only uses some of those variables:

julia> @macroexpand @calculate a + b data
quote
    #= untitled-a25e1fb3875a91a253aef47c6b6eba3d:8 =#
    #36#a = 1
    #37#b = 2
    #38#c = 3
    #36#a + #37#b
end

Is there a smarter way to do this? E.g. something that lets me know what symbols are actually contained in the expression, which I could use to filter the dictionary entries that are converted into variables inside the macro.

This is one way to do it.

interpolate_from_dict(ex::Expr, dict) = Expr(ex.head, interpolate_from_dict.(ex.args, Ref(dict))...)
interpolate_from_dict(ex::Symbol, dict) = get(dict, ex, ex)
interpolate_from_dict(ex::Any, dict) = ex

Then you have

julia> interpolate_from_dict(f1, data)
:(1 + 2)

julia> eval(interpolate_from_dict(f1, data))
3

Why do you need expressions? Can you just use normal functions?

Expressions are nice for quickly writing down formulas that you find in papers. But functions would be fine as well. For instance, I don’t mind writing:

julia> f1(a, b) = a + b
f1 (generic function with 1 method)

Two drawbacks:

  1. The code becomes “hidden”, you can’t type f1 and see what it does anymore. But that’s not a real problem, as I do have the source anyway.
  2. How can I tell f1 who are a and b from the dictionary…?

… Oh, wait!

julia> f1(; a, b, _...) = a + b
f1 (generic function with 2 methods)

julia> f1(; data...)
3

Horray! :partying_face:

Thanks @dpsanders!

If you can use functions, I don’t quite see why the simple

function f1(data)
  return data[:a] + data[:b]
end

does not solve the problem. Am I missing some complication?

Yes, of course I can use functions. What I was looking for is a way to write code copying it straight from e.g. papers or textbooks. One thing I like of Julia is how easy is to write code as math formulas. So it’s nice to have a way to write a + b instead of data[:a] + data[:b] when you work with data from dictionaries.

(The example of a+b is perhaps too simple. The visual difference is greater when the formulas are more complicated, with many variables and combined operations.)

But in the end, it is just a cosmetic issue - in situations when you don’t really care about optimal code.

An interesting use case.

Perhaps

using Parameters

function foo(data :: Dict)
  @unpack a,b = data
  return a+b
end

would be easier?

I didn’t know of Parameters.jl. Thank you!