ConstructFrom.jl - Construct objects without spelling out their types

Over the past couple of weeks, I’ve frequently had to write code like the following.

struct MyType
    data::Dict{Int, Dict{Float64, String}}
end


MyType() = MyType(Dict{Int, Dict{Float64, String}}()) 
                 #^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

function Base.insert!(m::MyType, i, x, s)
    get!(m.data, i, Dict{Float64, String}())[x] = s
                   #^^^^^^^^^^^^^^^^^^^^^
end

The issue here is that at the highlighted places, I have to repeat either parts or all of the type of data without providing any meaningful additional information. This seems unnecessarily tedious to write, read, and maintain.

Fortunately, there is an easy way to avoid this: Julia inserts an implicit convert() in many places, so if we define a Default type which “converts” itself to any type T simply by constructing a new T(), then instead of repeating the long and complicated types we can simply write Default() in the places marked above.

I’ve trialed a slightly more general version of this Default type in the ConstructFrom.jl package, and so far I’ve been quite happy with the result. The one additional twist introduced by ConstructFrom.jl is that instead of restricting ourselves to constructors of the form T(), it allows the user to pass arguments which are then forwarded to the constructor of T. For example:

julia> vec::Vector{Vector{Int}} = construct_from(undef, 3);

julia> vec
3-element Vector{Vector{Int64}}:
 #undef
 #undef
 #undef

I’m now playing with the thought of registering my package on the Julia registry, but before doing so I thought I’d first ask for some input here. In particular, I’m quite surprised that a package like this doesn’t seem to exist already (it tackles a fairly common problem, it has a very simple solution (6 lines of relevant code), and it has a precedent in the form of Rust’s Default trait). Is this issue already addressed by some other language feature or package, or is there some fundamental problem with my solution that I have not discovered yet?

2 Likes

Interesting approach! This kind of code duplication (types) happens from time to time, but not that often – probably that’s why there was no similar package before.

Some unintuitive and potentially confusing behavior, in addition to limitations from README:

julia> b = a::Dict = construct_from();
julia> a
Dict{Any, Any}()
# b remains construct_from()
julia> b
construct_from()

# works:
julia> c::Dict{Int} = Dict{Int,Any}()
Dict{Int64, Any}()
# doesn't:
julia> d::Dict{Int} = construct_from()
ERROR: MethodError: no method matching (Dict{Int64})()

Not a fault of your package, that’s just how Julia works.

1 Like

Thanks for you input, @aplavin! I’ve decided to go ahead and register my package.