Is there a difference between `T::Type` and `::Type{T} ... where T` in a method si

Is there a difference between T::Type and ::Type{T} ... where T in a method signature?

Note that the original poster on Slack cannot see your response here on Discourse. Consider transcribing the appropriate answer back to Slack, or pinging the poster here on Discourse so they can follow this thread.
(Original message :slack:) (More Info)

Answer: Yes, and it’s due to specialization.

T::Type allows the compiler to decide if it needs to specialize or not. If T is used in the function body, it will do so. If T is passed through to another function, it will not.

::Type{T} ... where T forces the compiler to specialize the function.

Specialization means a separate method is created and compiled for each new type passed to the function. If the method doesn’t do anything special hint hint based on the type, then there’s probably no benefit to doing this, with potentially substantial cost. The behavior is described here: Performance Tips · The Julia Language

Thanks to @jakobnissen @mkitti @Mason for the information!

3 Likes

Great question.

I also posted on using the methods method to see what specializations were happening or not happening. This is how I check myself to see if I understand what Julia is doing under-the-hood.

Here’s the version with no specialization:

julia> f(T::Type) = T
f (generic function with 1 method)
julia> methods(f).ms[1].specializations
svec()
julia> f(Int64)
Int64
julia> methods(f).ms[1].specializations
svec(#undef, #undef, #undef, #undef, #undef, #undef, #undef, MethodInstance for f(::Type{T} where T))

julia> f(Float64)
Float64

julia> methods(f).ms[1].specializations
svec(#undef, #undef, #undef, #undef, #undef, #undef, #undef, MethodInstance for f(::Type{T} where T))

Despite calling f with both Int64 and Float64 as arguments, only a single MethodInstance is needed to handle both arguments.

Here is the version with specialization:

julia> g(::Type{T}) where T = T
g (generic function with 1 method)
julia> methods(g).ms[1].specializations
svec()
julia> g(Int64)
Int64
julia> methods(g).ms[1].specializations
svec(MethodInstance for g(::Type{Int64}), #undef, #undef, #undef, #undef, #undef, #undef, #undef)
julia> g(Float64)
Float64
julia> methods(g).ms[1].specializations
svec(MethodInstance for g(::Type{Int64}), MethodInstance for g(::Type{Float64}), #undef, #undef, #undef, #undef, #undef, #undef)

Now we have a MethodInstance that is specialized for Int64 and another MethodInstance that is specialized for Float64.

methods returns a Base.MethodList with two fields, ms and mt. ms is just a Vector{Method}. mt is a Core.MethodTable. Both contain a wealth of information about the resulting methods.

2 Likes