Unnecessary type parameters

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 :stuck_out_tongue:

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.

julia> struct MyReal
           s::Real
       end

julia> Base.:(+)(a::MyReal, b::MyReal) = MyReal(a.s + b.s)

julia> MyReal(1) + MyReal(3.0)
MyReal(4.0)

julia> MyReal(1) + MyReal(3)
MyReal(4)

julia> struct MyRealConcrete{T<:Real}
           s::T
       end

julia> Base.:(+)(a::MyRealConcrete, b::MyRealConcrete) = MyRealConcrete(a.s + b.s)

julia> MyRealConcrete(1) + MyRealConcrete(3.0)
MyRealConcrete{Float64}(4.0)

julia> MyRealConcrete(1) + MyRealConcrete(3)
MyRealConcrete{Int64}(4)

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.

I agree:

1 Like

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 :stuck_out_tongue:

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
1 Like

That thread includes a lot of what my question was about, thanks. To bad it seems like it will possibly first be implemented in 2.0.

1 Like

Checkout [ANN] ConcreteStructs.jl - Cut the boilerplate when concretely parameterizing structs

6 Likes