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