I have a nested dict like this
function_dict_1 = Dict(
:f => Dict(
:func1 => x -> x^2
)
)
I want to call a strongly typed function, which receives this dict as an argument.
My first thought was to type it like this:
dict_arg::Dict{Symbol, Dict{Symbol, <:Function}}
However, I got an unmatched function call error.
I am confused, since it seems to work with other datatypes:
string_dict = Dict(
:s => Dict(
:string1 => "a"
)
)
typeof(string_dict) <: Dict{Symbol, Dict{Symbol, String}}
>> true
as well as with not-nested dicts:
function_dict_2 = Dict(
:func1 => x -> x^2
)
typeof(function_dict_2) <: Dict{Symbol, <:Function}
>> true
I tried the following types:
Dict{Symbol, Dict{Symbol, <:Function}}
Dict{Symbol, Dict{Symbol, Function}}
AbstractDict{Symbol, AbstractDict{Symbol, <:Function}}
AbstractDict{Symbol, AbstractDict{Symbol, Function}}
which all did not work.
What is the correct way to type for this variable?
function_dict_1::Dict{Symbol, <:Dict{Symbol, <:Function}}
You could also solve this with an explicit generic parameter:
function_dict_1::Dict{Symbol, Dict{Symbol, F}} where F <: Function
You're probably wondering why these both work when Dict{Symbol, Dict{Symbol, <:Function}}
doesn't; after all, isn't Dict{Symbol, Dict{Symbol, <:Function}}
the same as Dict{Symbol, Dict{Symbol, F}} where F <: Function
? No, because the implicit generic parameter is always scoped to the innermost {}
containing the <:
, so Dict{Symbol, Dict{Symbol, <:Function}}
is actually the same as Dict{Symbol, Dict{Symbol, F where F <: Function}}
.
Now, except for tuples, T1 <: T2
does not imply that SomeType{T1} <: SomeType{T2}
. So even though typeof(function_dict_1).parameters[2]
is Dict{Symbol, var"#1#2"}
(where var"#1#2"
is the name of the type of the anonymous function), and Dict{Symbol, var"#1#2"} <: Dict{Symbol, <:Function}
(but !(Dict{Symbol, var"#1#2"} <: Dict{Symbol, Function})
!), we do not have that Dict{Symbol, Dict{Symbol, var"#1#2"}} <: Dict{Symbol, Dict{Symbol, <:Function}}
.
So why do the two answers I gave above work while Dict{Symbol, Dict{Symbol, <:Function}}
doesn't? Because the implicit generic parameter is scoped to the innermost {}
containing <:Function
, the outer dict type is a concrete type, not a UnionAll
.
julia> dump(Dict{Symbol, Dict{Symbol, <:Function}})
Dict{Symbol, Dict{Symbol, <:Function}} <: AbstractDict{Symbol, Dict{Symbol, <:Function}}
slots::Memory{UInt8}
keys::Memory{Symbol}
vals::Memory{Dict{Symbol, <:Function}}
ndel::Int64
count::Int64
age::UInt64
idxfloor::Int64
maxprobe::Int64
Since function_dict_1
is not exactly this type, it cannot be a subtype of it.
Now, compare this to what we get for other types. For subtyping to work (except for tuples), we need either a UnionAll
or an abstract type as the outermost type.
julia> dump(Dict{Symbol, <:Function})
UnionAll
var: TypeVar
name: Symbol #s66
lb: Union{}
ub: Function <: Any
body: Dict{Symbol, var"#s66"<:Function} <: AbstractDict{Symbol, var"#s66"<:Function}
slots::Memory{UInt8}
keys::Memory{Symbol}
vals::Memory{var"#s66"}
ndel::Int64
count::Int64
age::UInt64
idxfloor::Int64
maxprobe::Int64
julia> dump(Dict{Symbol, Dict{Symbol, F where F <: Function}})
Dict{Symbol, Dict{Symbol, Function}} <: AbstractDict{Symbol, Dict{Symbol, Function}}
slots::Memory{UInt8}
keys::Memory{Symbol}
vals::Memory{Dict{Symbol, Function}}
ndel::Int64
count::Int64
age::UInt64
idxfloor::Int64
maxprobe::Int64
julia> dump(Dict{Symbol, Dict{Symbol, F}} where F <: Function)
UnionAll
var: TypeVar
name: Symbol F
lb: Union{}
ub: Function <: Any
body: Dict{Symbol, Dict{Symbol, F<:Function}} <: AbstractDict{Symbol, Dict{Symbol, F<:Function}}
slots::Memory{UInt8}
keys::Memory{Symbol}
vals::GenericMemory{:not_atomic, Dict{Symbol, F<:Function}, Core.AddrSpace{Core}(0x00)}
ndel::Int64
count::Int64
age::UInt64
idxfloor::Int64
maxprobe::Int64
julia> dump(Dict{Symbol, <:Dict{Symbol, <:Function}})
UnionAll
var: TypeVar
name: Symbol #s67
lb: Union{}
ub: UnionAll
var: TypeVar
name: Symbol #s68
lb: Union{}
ub: Function <: Any
body: Dict{Symbol, var"#s68"<:Function} <: AbstractDict{Symbol, var"#s68"<:Function}
slots::Memory{UInt8}
keys::Memory{Symbol}
vals::Memory{var"#s68"}
ndel::Int64
count::Int64
age::UInt64
idxfloor::Int64
maxprobe::Int64
body: Dict{Symbol, var"#s67"<:(Dict{Symbol, <:Function})} <: AbstractDict{Symbol, var"#s67"<:(Dict{Symbol, <:Function})}
slots::Memory{UInt8}
keys::Memory{Symbol}
vals::Memory{var"#s67"}
ndel::Int64
count::Int64
age::UInt64
idxfloor::Int64
maxprobe::Int64
julia>
In short, the first <:
in Dict{Symbol, <:Dict{Symbol, <:Function}}
is necessary to let the second <:
make it out of its own {}
, so that the whole type can be a UnionAll
.