# Dictionaries with abstract types for keys/values as function arguments

I am currently writing a library that for a number of applications needs to take a dictionary of matrices as arguments. In order to make the functions reasonably general, I wanted to make the key/value types in the function arguments abstract. e.g. something like:

``````function print_dict(dict::AbstractDict{<:Real, AbstractArray{<:Real, 2}})
print(dict)
end
``````

(This is not a real function I want to write, just for demonstration of the argument type)

Calling this function with a `dict` of what I assumed to be a valid subtype of the function argument

``````dict = Dict{Int, Array{Float32, 2}}()

dict[1]  = [10.3 3.4 ; 2.3 4]

print_dict(dict)
``````

however gives the following error:

``````ERROR: LoadError: MethodError: no method matching print_dict(::Dict{Int64,Array{Float32,2}})
Closest candidates are:
print_dict(::AbstractDict{var"#s1",AbstractArray{var"#s2",2} where var"#s2"<:Real} where var"#s1"<:Real) at ***/dict_weirdness.jl:1
Stacktrace:
[1] top-level scope at ***/dict_weirdness.jl:10
[2] include(::String) at ./client.jl:457
[3] top-level scope at REPL[1]:1
in expression starting at ***/dict_weirdness.jl:10
``````

I expected this to work as you can do things like `AbstractArray{<:Real, 2}` as functional argument types no problem.

Can anyone explain why this does not work, and perhaps suggest any workarounds / better practises?

It’s not a complete answer, but:

``````julia> d=Dict([1 => [1.0 2.0;]])
Dict{Int64,Array{Float64,2}} with 1 entry:
1 => [1.0 2.0]

julia> print_dict4(d :: AbstractDict{T1, Matrix{T2}}) where {T1 <: Real, T2 <: Real} = println(d);

julia> print_dict4(d)
Dict(1 => [1.0 2.0])
``````

whereas

``````julia> print_dict5(d :: AbstractDict{T1, AbstractMatrix{T2}}) where {T1 <: Real, T2 <: Real} = println(d);

julia> print_dict5(d)
ERROR: MethodError: no method matching print_dict5(::Dict{Int64,Array{Float64,2}})
``````
``````julia> function print_dict(dict::AbstractDict{<:Real, <:AbstractArray{<:Real, 2}})

print(dict)

end
``````

works. (now I have to catch a bus, maybe someone else can explain more)

2 Likes

That’s the point I was missing:

``````julia> d=Dict([1 => [1.0 2.0;]])
Dict{Int64,Array{Float64,2}} with 1 entry:
1 => [1.0 2.0]

julia> isa(d, Dict{<:Real,<:Matrix})
true

julia> isa(d, Dict{<:Real,Matrix})
false
``````

and so the dispatch fails without the additional `<:`

1 Like

Thank you both, this is enlightening. For some reason, I had not expected the need of `<:` before the `AbstractArray`. Typically you don’t need this when the function argument is of `AbstractArray` type, e.g.

``````print_array(array::AbstractArray{<:Real, 2}) = println(array)
``````

will work fine without the additional `<:`

But you do have the additional `<:` in your last example – it’s the one in front of the `Real`. This is really the point made in the docs in a warning box:

This last point is very important: even though `Float64 <: Real` we DO NOT have `Point{Float64} <: Point{Real}` .

Ah okay yes I see.

Is the reason you don’t need the `<:` on the first level of argument type a syntactic nicety then?

i.e. I mean that

``````print_array(array::AbstractArray{<:Real, 2}) = println(array)
``````

does not need to be

``````print_array(array::<:AbstractArray{<:Real, 2}) = println(array)
``````

This is just the same fact that Julia types are invariant (https://docs.julialang.org/en/v1/manual/types/#Parametric-Composite-Types) all over again.

If you have a parametric type `Foo{T}` and types `A <: B`, then it is not true that `Foo{A} <: Foo{B}`. It is, however, true that:

``````Foo{A} <: (Foo{X} where X <: B)
``````

and, equivalently, that:

``````Foo{A} <: Foo{<:B}
``````

In both cases you are saying "Foo{A} is a subtype of the collection of all types `Foo{X}` where `X` is some subtype of `B`".

In your case, `Array{Float64, 2} <: AbstractArray{<:Real, 2}`, but that does not mean that `Dict{Int, Array{Float64, 2}} <: Dict{Int, AbstractArray{<:Real, 2}}`.

``````Dict{Int, Array{Float64, 2}} <: (Dict{Int, A} where A <: AbstractArray{<:Real, 2})
``````Dict{Int, Array{Float64, 2}} <: Dict{Int, <:AbstractArray{<:Real, 2}}
which means, roughly, "Dict{Int, Array{Float64, 2}} is a member of the set of all types `Dict{Int, A}` in which `A` is some kind of `AbstractArray{R, 2}` and `R` is some kind of `Real`.