I have a function that accepts a dictionary as input where the arguments will always be string keys with values that are a vector of real numbers. They may in practice be integers or floats or whatever. What is important is that this function does not work if the arguments are not real numbers (So a vector of strings, or complex, etc, are not valid inputs)
julia> function foo(input_dict::Dict{String, <:AbstractVector{<:Real}})
println(input_dict)
end
foo (generic function with 1 method)
If I declare a dictionary like so
julia> a = Float32.(rand(3));
julia> b = Float64.(rand(3));
julia> c = Integer.(ceil.(rand(3).*10));
julia> my_dict = Dict("key1"=>a, "key2"=>b, "key3"=>c)
Dict{String, Vector} with 3 entries:
"key2" => [0.223777, 0.394267, 0.0128682]
"key3" => [6, 3, 10]
"key1" => Float32[0.788629, 0.146732, 0.93549]
then
julia> typeof(my_dict)
Dict{String, Vector}
and the call to foo
will fail, it seems because of the fact that my_dict
has the type {String, Vector}
(without the extra qualifier on type).
julia> foo(my_dict)
ERROR: MethodError: no method matching foo(::Dict{String, Vector})
Closest candidates are:
foo(::Dict{String, <:AbstractVector{<:Real}}) at REPL[5]:1
Stacktrace:
[1] top-level scope
@ REPL[6]:1
On the other hand, this will work fine if all the value
entries in the dict have the same type
julia> a = rand(3);
julia> b = rand(3);
julia> my_other_dict = Dict("a"=>a, "b"=>b)
Dict{String, Vector{Float64}} with 2 entries:
"b" => [0.700047, 0.765836, 0.603907]
"a" => [0.623392, 0.476285, 0.500553]
julia> foo(my_other_dict)
Dict("b" => [0.7000467111406179, 0.7658355327841554, 0.6039073311484552], "a" => [0.6233916490131216, 0.4762846111664425, 0.500552988508849])
This function is supposed to be part of a package, so ideally the fix involves changing the definition of foo
rather than requiring the dictionary to be typed during creation.
What I’m wondering about is the “correct” way to fix this in terms the most Julia-esque way of doing it. I’m not terribly concerned about efficiency as this isn’t going to be handling high volumes of data.
My first thought was to do something with overloading. I.E
function foo(key::String, value::AbstractVector{<:Real})
...
end
# or...
function foo(input::Dict{String, <:AbstractVector{<:Real}})
...
end
# Then...
function foo(input::Dict{String, <:AbstractVector})
for (key, value) in input
foo(key, value)
end
# Or perhaps could also construct a dict for each entry?
for (key, value) in input
foo(Dict(key=>value))
end
end
My only problem with this approach is that foo
ultimately deals with writing to a file that already exists. It wouldn’t be great to have it write the first few keys and then fail halfway through because one key has the wrong type, which was why I was hoping to structure foo
in a way that it fails before trying to write anything at all.