mario90
mario90

Reputation: 33

Unpack dict entries inside a function

I want to unpack parameters that are stored in a dictionary. They should be available inside the local scope of a function afterwards. The name should be the same as the key which is a symbol.

macro unpack_dict()
    code = :()
    for (k,v) in dict
        ex = :($k = $v)
        code = quote
            $code
            $ex
        end
    end
    return esc(code)
end

function assign_parameters(dict::Dict{Symbol, T}) where T<:Any
    @unpack_dict
    return a + b - c
end

dict = Dict(:a => 1,
            :b => 5,
            :c => 6)

assign_parameters(dict)

However, this code throws:

LoadError: UndefVarError: dict not defined

If I define the dictionary before the macro it works because the dictionary is defined.

Does someone has an idea how to solve this? Using eval() works but is evaluated in the global scope what I want to avoid.

Upvotes: 3

Views: 1370

Answers (2)

BallpointBen
BallpointBen

Reputation: 13999

In Julia 1.7, you can simply unpack named tuples into the local scope, and you can easily "spread" a dict into a named tuple.

julia> dict = Dict(:a => 1, :b => 5, :c => 6)
Dict{Symbol, Int64} with 3 entries:
  :a => 1
  :b => 5
  :c => 6

julia> (; a, b, c) = (; sort(dict)...)
(a = 1, b = 5, c = 6)

julia> a, b, c
(1, 5, 6)

(The dict keys are sorted so that the named tuple produced is type-stable; if the keys were produced in arbitrary order then this would result in a named tuple with fields in arbitrary order as well.)

Upvotes: 1

Bogumił Kamiński
Bogumił Kamiński

Reputation: 69949

If you want to unpack them then the best method is to simply unpack them directly:

function actual_fun(d)
    a = d[:a]
    b = d[:b]
    c = d[:c]
    a+b+c
end

This will be type stable, relatively fast and readable.

You could, for instance, do something like this (I present you two options to avoid direct assignment to a, b, and c variables):

called_fun(d) = helper(;d...)
helper(;kw...) = actual_fun(;values(kw)...)
actual_fun(;a,b,c, kw...) = a+b+c

function called_fun2(d::Dict{T,S}) where {T,S}
    actual_fun(;NamedTuple{Tuple(keys(d)), NTuple{length(d), S}}(values(d))...)
end

and now you can write something like:

julia> d = Dict(:a=>1, :b=>2, :c=>3, :d=>4)
Dict{Symbol,Int64} with 4 entries:
  :a => 1
  :b => 2
  :d => 4
  :c => 3

julia> called_fun(d)
6

julia> called_fun2(d)
6

But I would not recommend it - it is not type stable and not very readable.

AFACT other possibilities will have similar shortcomings as during compile time Julia knows only types of variables not their values.

EDIT: You can do something like this:

function unpack_dict(dict)
    ex = :()
    for (k,v) in dict
        ex = :($ex; $k = $v)
    end
    return :(myfun() = ($ex; a+b+c))
end

runner(d) = eval(unpack_dict(d))

and then run:

julia> d = Dict(:a=>1, :b=>2, :c=>3, :d=>4)
Dict{Symbol,Int64} with 4 entries:
  :a => 1
  :b => 2
  :d => 4
  :c => 3

julia> runner(d)
myfun (generic function with 1 method)

julia> myfun()
6

but again - I feel this is a bit messy.

Upvotes: 1

Related Questions