Hi there,
I would like to define a set of structs, which share their name (and thus type), but specialize their fields depending on their parametric type:
# abstract specialization types (of which there may be more than one parent type)
abstract type AbstractSpecializeParent end
# hierarchy structure of parent type
abstract type SpecializeTypeA <: AbstractSpecializeParent end
abstract type SpecializeTypeB <: AbstractSpecializeParent end
# struct of type ContainerType1 specialized to SpecializeTypeA
mutable struct ContainerType1{SpecializeTypeA}
a
b
c
end
# struct of type ContainerType1 specialized to SpecializeTypeB
mutable struct ContainerType1{SpecializeTypeB}
d
e
end
which then allows me to do stuff like:
# a function which is specialized for ContainerType1 with subtype SpecializeTypeA
somefunction(obj::ContainerType1{T}) where {T<:SpecializeTypeA} = (obj.a + obj.b / obj.c,T)
As you see, each struct would have a different set of fields. However, for obvious reasons such definitions are not possible as that gives an Error: invalid redefinition of constant ContainerType1
.
I want to do this anyway for several reasons:
- The fields depend on the parametric type,
- dispatch functions depending on the type and parametric type, and
- use the parametric type inside of the function.
I cannot use fields inside of the structs, which contain the specialization as symbols, because the parametric types have an hierarchy and I need to dispatch on that. To add further complexity: several “AbstractSpecializeParent” types and hierarchies exist in parallel and I need to dispatch on each of those individually. Thus, I cannot just do
mutable struct ContainerType1SpecializeTypeASpecializeTypeZ <: ?????
specializeA::Symbol
specializeZ::Symbol
a
b
c
end
because it would need to inherit from several different specialization types!
I have found a workaround using a container struct to wrap the highly specialized concrete types. These concrete types now have these really ugly names, which I also wanted to avoid, but that’s just aesthetics:
# abstract super type of hidden structs
abstract type AbstractConcreteType1 end
# type specialized container for hiding structs
struct ContainerType1{T<:AbstractSpecializeParent}
hiddenstruct::AbstractConcreteType1
end
# fields of the hidden concrete type act as fields of the super type (cannot access field hiddenstruct anymore)
Base.getproperty(obj::ContainerType1, sym::Symbol) = getproperty(getfield(obj, :hiddenstruct), sym)
# first hidden struct specialized for abstract type SpecializeTypeA
mutable struct ContainerType1SpecializeTypeA <:AbstractConcreteType1
a
b
c
end
# quasi constructor with default field values
ContainerType1(::Type{T}) where {T<:SpecializeTypeA} = ContainerType1{T}(ContainerType1SpecializeTypeA(1,2,3))
# second hidden struct specialized for abstract type SpecializeTypeB
mutable struct ContainerType1SpecializeTypeB <:AbstractConcreteType1
d
e
end
# quasi constructor with default field values
ContainerType1(::Type{T}) where {T<:SpecializeTypeB} = ContainerType1{T}(ContainerType1SpecializeTypeB(4,5))
This allows me to very quickly and rapidly add following:
# sub type of specialization type
abstract type SpecializeTypeASub1 <: SpecializeTypeA end
# new conrete type
mutable struct ContainerType1SpecializeTypeASub1 <: AbstractConcreteType1
a
b
c
d
end
ContainerType1(::Type{T}) where {T<:SpecializeTypeASub1} = ContainerType1{T}(ContainerType1SpecializeTypeASub1(1,2,3,4))
Now the function somefunction()
works exactly the same for this new subtype, however, where necessary, I can specialize functions:
somefunction(obj::ContainerType1{T}) where {T<:SpecializeTypeASub1} = (obj.a/obj.b - obj.c*obj.d,T)
The question I have is … should I even do this, or does this negatively impact performance?
I do realize that
struct ContainerType1{T<:AbstractSpecializeParent}
hiddenstruct::AbstractConcreteType1
end
negatively impacts performance, because the compiler cannot generate specialized code, because the actual type of the field hiddenstruct
is hidden.
I can work around that as well given
struct ContainerType1{S<:AbstractConcreteType1,T<:AbstractSpecializeParent}
hiddenstruct::S
end
Then I need to adapt the quasi constructors and the function calls to incorporate the parametric type. (I have not yet gotten that to work.)
Besides the impact on performance, do you know of a better, less contrived approach I could use to realize this?
Thanks in advance!
PS: If any part of what I said does not make any sense to you, I am very happy to further explain. Additionally, if you think I structured my post badly in a particular way, please give me notice so I can improve it.