Quite often I have to use the type parameters to make the struct concrete/type stable, but I never need to use that parameter for dispatch. So the type parameter becomes unnecessary in some sense (and ugly). For example
abstract struct AType end
struct Subtype1 <: Atype end
struct Subtype2 <: Atype end
struct Othertype{T<:AType}
s::T
end
If I would have
struct Othertype
s::AType
end
OtherType(Subtype1()) would not be concrete.
I feel like one should be able to do as in the second version, and still have it type stable, instead of caring around unnecessary type parameters.
I know there are probobly macros that can help with this, but I am more interested in what is the reasoning not have it in the language by default
I feel like one should be able to do as in the second version, and still have it type stable, instead of caring around unnecessary type parameters.
But non concrete types such as AType can not lead to concrete structs by definition. Not sure why you find the type parameter to be unnecessary here?
In particular consider this:
julia> abstract type AType end
struct Subtype1 <: AType end
struct Subtype2 <: AType end
struct Othertype{T<:AType}
s::T
end
julia> struct Othertype2
s :: AType
end
and check the inferred element types of arrays of Othertype and Othertype2:
julia> using Test
julia> @inferred [Othertype2(Subtype1()), Othertype2(Subtype2())][1]
Othertype2(Subtype1())
julia> @inferred [Othertype(Subtype1()), Othertype(Subtype2())][1]
ERROR: return type Othertype{Subtype1} does not match inferred return type Othertype
This indicates that the compiler knows the element type exactly for Othertype2 but not for Othertype. There might be occasions where you want the compiler to have this information.
Here’s an aside that you might want to consider:
Methods for structs with type-parameters would need to be re-compiled for each combination, whereas they won’t need to be if the struct isn’t concretely typed. The language allows this.
In the first case the function + needs to be compiled just once for MyReal arguments, whereas in the second case it’ll need to be compiled for each combination of MyRealConcrete{T}. Performance will likely be better with the latter, but there might be occasions where you want the former.
Woops, replied to wrong post.
Ok, so I can see that there are instances where both options are useful.
The reason I want to make everything concrete, is because I notice better performance in my code.
What I mean with “unnecessary” is that I will never need to dispatch on the type parameter, it just there to make it concrete/get better performance. For example, In my code I have a struct (lets call it “MyStruct”) with like 4-5 fields, and I need to have their types as type parameters, which makes for extra code. Also when getting errors, the stacktrace takes up like 5 rows because the type-name becomes so long, making it difficult to read the error etc. Furthermore, if I need MyStuct as a field in some other struct, I need to make its type an type-parameter aswell, so basically everything gets put in the type field all the time
I think there is a package that solves this using macros, but it works by putting it as type parameters. It would be nice if the compiler could keep track of some of the types “in the background” without having them as type parameters.
struct MyType{A<:A1, B<:A2, C<:A3}
a::A
b::B
c::C
end
instead of
struct MyType
@concrete a::A1
@concrete b::A2
@concrete c::A3
end