How to know what types will the type signature match?

When writing some function signatures, I sometimes want to know what types will match them.
For example,

f(::AbstractVector{Real}) = 1
f(::AbstractVector{<: Real}) = 2

They are quite similar and sometimes could confuse. Is there a way for me to examine what types in the runtime will be sent to 1 and what will be sent to 2? That’s really great help!

is this what you’re looking for?

julia> f(x::Integer) = x+1
f (generic function with 1 method)

julia> f(x::AbstractFloat) = x+2
f (generic function with 2 methods)

julia> methods(f(0.1))
# 0 methods for generic function "(::Float64)":

julia> methods(f(2))
# 0 methods for generic function "(::Int64)":

I am afraid not. I do not quite understand what

julia> methods(f(0.1))
# 0 methods for generic function "(::Float64)":

julia> methods(f(2))
# 0 methods for generic function "(::Int64)":

stand for.
In your example, methods(f(0.1)) first evaluates f(0.1) then evaluates methods((::Float64)) so I do not see their meaning.
I actually want to know the set of all types of x that can satisfy f(x::Integer) (of course it is straightforward in your example). Besides, you are assuming f's output has the same type as the input type, but it is often hard to do that for a general f in practice.

Yes, the examples should probably have read something like:

julia> methods(f, (Float64,))
# 1 method for generic function "f":
[1] f(x::AbstractFloat) in Main at REPL[3]:1

julia> methods(f, (Int,))
# 1 method for generic function "f":
[1] f(x::Integer) in Main at REPL[2]:1

But I think it does not really solve your problem: it tells you which method is used for a particular set of arguments types, rather than telling you the list of all possible argument types for a given method.

I don’t know how to solve your problem directly, but I would perhaps simplify it to the following one, which might be easier to solve: given a type T, is it possible to list all subtyes of T?

A good start for this simplified question would be to use subtypes:

julia> subtypes(AbstractVector{Real})
10-element Array{Any,1}:
 Base.LogicalIndex{Real,A} where A<:(AbstractArray{Bool,N} where N)                                                                                      
 Base.ReinterpretArray{Real,1,S,A} where A<:AbstractArray{S,1} where S                                                                                   
 Base.ReshapedArray{Real,1,P,MI} where MI<:Tuple{Vararg{Base.MultiplicativeInverses.SignedMultiplicativeInverse{Int64},N} where N} where P<:AbstractArray
 PermutedDimsArray{Real,1,perm,iperm,AA} where AA<:AbstractArray where iperm where perm                                                                  
 SparseArrays.AbstractSparseArray{Real,Ti,1} where Ti                                                                                                    
 SubArray{Real,1,P,I,L} where L where I where P                                                                                                          
julia> subtypes(AbstractVector{<: Real})
14-element Array{Any,1}:
 AbstractRange{T} where T<:Real                                                                                                                                     
 Base.LogicalIndex{T,A} where A<:(AbstractArray{Bool,N} where N) where T<:Real                                                                                      
 Base.ReshapedArray{T,1,P,MI} where MI<:Tuple{Vararg{Base.MultiplicativeInverses.SignedMultiplicativeInverse{Int64},N} where N} where P<:AbstractArray where T<:Real
 Core.Compiler.AbstractRange{T} where T<:Real                                                                                                                       
 Core.Compiler.LinearIndices{1,R} where R<:Tuple{Core.Compiler.AbstractUnitRange{Int64}}                                                                            
 DenseArray{T,1} where T<:Real                                                                                                                                      
 LinearIndices{1,R} where R<:Tuple{AbstractUnitRange{Int64}}                                                                                                        
 PermutedDimsArray{T,1,perm,iperm,AA} where AA<:AbstractArray where iperm where perm where T<:Real                                                                  
 SparseArrays.AbstractSparseArray{Tv,Ti,1} where Ti where Tv<:Real                                                                                                  
 SubArray{T,1,P,I,L} where L where I where P where T<:Real                                                                                                          
 Test.GenericArray{T,1} where T<:Real                                                                                                                               
 Union{ReinterpretArray{T,1,S,A} where A<:AbstractArray{S,1} where S, ReinterpretArray{T,1,S,A} where A<:AbstractArray{S,1} where S} where T<:Real                  

(you could also apply it recursively to replace all abstract types in the list with the list of their own subtypes, but I would tend to think that this would make the list grow qhickly to unmanageable length)

1 Like

Thanks, I have thought about this method and had tried some examples. But one thing I do not understand:

julia> subtypes(Vector{Integer})
0-element Array{Type,1}

julia> subtypes(Vector{<: Integer})
0-element Array{Type,1}

They both give an empty array. However,

julia> subtypes(Integer)
3-element Array{Any,1}:

julia> Vector{Bool} <: Vector{<: Integer}

julia> Vector{Signed} <: Vector{<: Integer}

julia> Vector{Unsigned} <: Vector{<: Integer}

Aren’t Vector{Bool}, Vector{Signed} and Vector{Unsigned} and their subtypes (if exist) the subtypes of Vector{<: Integer}? So, is this method incomplete or somewhere I made a mistake?

Ah, yes, that makes sense: T1 <: T2 is true if all values of type T1 are also of type T2. But this does not mean that T1 has to be a subtype of T2 (in the sense that the supertype of T1 would be T2): there are many ways that T1 <: T2 without T1 and T2 being related in the type hierarchy.

Besides the examples you gave, I can think of:

julia> subtypes(Union{Int32, Float32})
0-element Array{Type,1}

julia> Int32 <: Union{Int32, Float32}

looking only at the type hierarchy, Int32 has supertype Signed, which itself has supertype Integer, which itself has supertype Real and so on. Nowhere in the hierarchy does Union{Int32, ...} appear, yet obviously any Int32 value isa Union{Int32, Float32}.

So you’re right: using subtypes is not going to help you with your problem. Hopefully someone more knowledgeable will chime in with another idea…

1 Like

this example can help a little:

abstract type AbstractExample{T} end

struct ConcreteExample{T} <: AbstractExample{T}

function test_x(x::AbstractExample{Real})
    return 1
function test_x(x::AbstractExample{<:Real})
    return 2

a = ConcreteExample{Real}(2) 
#a have the parametric type as Real, not using the dispatch
#a = ConcreteExample{Real}(2) 

b = ConcreteExample(2)
#b is using the type of 2 (Int64)
#b = ConcreteExample{Int64}(2) 

test_x(a) ##1
test_x(b) ##2

Real is a container (abstract) type, so only when you create an struct with the type Real, the first function it’s called. in any other case, a concrete implementation will be called, in this case a int64. the Real acts as a union of all subtypes (Integers,Rationals,Floats,etc)
here are some examples:

Vector{Real}<:AbstractVector{Real}  #true 
Vector{Float64}<:AbstractVector{<:Real} #also true 

Edit: added some dank images to try to explain, please correct me if i’m wrong (i ommited some details, like the parameters of ForwardDiff.Dual, but this is what i can deduce)