Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unpack dict entries inside a function

Tags:

julia

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.

like image 405
mario90 Avatar asked Jul 11 '26 18:07

mario90


2 Answers

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.

like image 169
Bogumił Kamiński Avatar answered Jul 13 '26 07:07

Bogumił Kamiński


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.)

like image 44
BallpointBen Avatar answered Jul 13 '26 07:07

BallpointBen