How to call constructor of parametric family of types efficiently

I would like to call the constructor of several parametric families of types efficiently, while only having access to a concrete instance of a type. Also, I am trying to avoid having to write a separate method for each family.

For instance, given x::A{T}, I would like to call the constructor A, and not A{T}.

I came up with the following,

constructor = eval(Meta.parse(string(typeof(x).name)))

but is there a more efficient and/or elegant solution to this problem?

If they share a common supertype:

julia> abstract type B end

julia> struct A{T} <: B
          field
       end

julia> struct C <: B end

julia> A() = A{String}("hello")
A

julia> A{Int}(1) |> typeof |> supertype |> subtypes |> y -> filter(x -> x <: A, y)[1]()
A{String}("hello")

That is

function constructor(x::F) where F
   types = subtypes(supertype(F))
   return types[findfirst(x -> F <: x, types)]
end

But I do have to say, this doesn’t feel right to do. What’s your usecase beyond wanting to do this to save some methods? This strangely enough feels like a factory pattern and I can’t quite imagine how you’d end up with that architecture using julia in the first place.

1 Like

I am working on a module with a number of parametric types. Instances of these types can further be combined in arbitrary ways using an algebra that is defined on them. All types have a parametric type which corresponds to the type of the number field on which they are defined. This is similar to the first type parameter in Julia’s arrays.

Given such a composite type with an associated field type, I need to construct instances with the same hierarchical structure (based on the algebra) but a different field type.

This is a relatively hard problem, see e.g. the discussion on Stripping parameter from parametric types here on discourse, or the paragraph on Design Patterns with Parametric Methods in the documentation.


I would think that building a separate method for each family remains the most idiomatic solution. This is more or less what similar does, for example.




That being said, if you really want a fully generic implementation, something like this might work:

stripped_type(x) = stripped_type(typeof(x))
stripped_type(typ::DataType) = Base.typename(typ).wrapper

For example:

julia> struct A{T}
           x::T
       end

julia> a = A(42)
A{Int64}(42)

julia> stripped_type(a)(42.5)
A{Float64}(42.5)

Again, I would not recommend such a solution, which relies too much on internal details to my taste.

3 Likes

Thank you for the references!

As a matter of fact, I am extending similar :slight_smile:

I am tending towards using meta-programming for each family then. There are too many types to write this by hand. Depending on ones taste, that might still rely on a lot of internals. It will probably be the most performant solution though.

I am not able to accept an answer for some reason. But thank you for your input.

FYI https://github.com/JuliaObjects/ConstructionBase.jl is designed to solve this problem. You can use constructorof as-is if you are not doing anything fancy in the constructor.

4 Likes