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.
2 Likes
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
2 Likes
Why do you need expressions? Can you just use normal functions?
1 Like
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:
- 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.
- 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!
Thanks @dpsanders!
2 Likes
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?
2 Likes
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?
3 Likes
I didn’t know of Parameters.jl. Thank you!