Unexpected behaviour in nested DefaultDicts

I am using a structure where I want to use nested DefaultDicts. The outer one, I want to default to an empty DefaultDict, which in turn I want to have a default value of 1.0.

julia> d = DefaultDict{Int64, DefaultDict}(DefaultDict{Int64, Float64}(1.0))
DefaultDict{Int64,DefaultDict,DefaultDict{Int64,Float64,Float64}} with 0 entries

julia> d[1][1]
1.0

julia> d[1][1] = 4.0
4.0

julia> d[1][3]
1.0

julia> d[6][1]
4.0

I was not expecting the last result (which of course comes out the same whatever the value of the outer Dict index). I was expecting the outer DefaultDict to produce a new, empty inner DefaultDict and so default back to a 1.0. Instead, it seems to be using the existing inner DefaultDict as the new default for any new outer one.

Can you help me understand this and how to achieve my expected behaviour?

(I am new to Julia, and to programming in general, so if you think the issue is too blindingly obvious to need saying, please say it anyway.)

So what is happening is that

outer = DefaultDict{Int64, DefaultDict}(DefaultDict{Int64, Float64}(1.0))

is the same as:

inner = DefaultDict{Int64, Float64}(1.0)
outer = DefaultDict{Int64, DefaultDict}(inner)

So whenever outer needs a default value,
it returns inner.
Not a copy of inner, or a new DefaultDict equal to inner, but actually inner itself.
Which means that mutating that returned value, is literally mutating inner.


If rather than passing in a value, you pass in a function (or a constructor) as the default,
then DefaultDict will invoke that function and return the value.
You can use this to make it construct a new inner dict each time it wants one.

julia> construct_new_inner() = DefaultDict{Int64, Float64}(1.0);

julia> outer = DefaultDict{Int64, DefaultDict}(construct_new_inner)
DefaultDict{Int64,DefaultDict,var"##9#10"} with 0 entries

julia> outer[1][1]
1.0

julia> outer[1][1] = 4.0
4.0

julia> outer[1][3]
1.0

julia> outer[6][1]
1.0

Which can also be written as:

d = DefaultDict{Int64, DefaultDict}(()->DefaultDict{Int64, Float64}(1.0))
4 Likes