typesjuliatype-variables

Julia Type Parameters Nesting/Scope in Function Parameters


Following up from a question I posted earlier, I am trying to understand the details of type parameter scope in function methods.
For example:

f(x::Vector{<:Number}) = sum(x)
g(x::Vector{T}) where T<:Number = sum(x)

The <:Number constraint covers only the argument list of f:

julia> methods(f)
f(x::Array{#s1,1} where #s1<:Number)

but it seems that g is parameterized by it:

julia> methods(g)
g(x::Array{T,1}) where T<:Number

Is there a reason why "[y]ou generally want T to cover as little of the signature as possible" aside from style and limiting T's scope?
Are there any implications performance, dispatch, or code-generation wise?


Solution

  • Indeed f and g above are equivalent as Michael noted. You can dynamically check it by writing:

    julia> f(x::Vector{<:Number}) = sum(x)
    f (generic function with 1 method)
    
    julia> f(x::Vector{T}) where T<:Number = sum(x)
    f (generic function with 1 method)
    
    julia> methods(f)
    # 1 method for generic function "f":
    [1] f(x::Array{T,1}) where T<:Number in Main at REPL[2]:1
    

    And Julia tells you that the method definition was overwritten (so that it was deemed to be equivalent).

    However, in general scoping does matter. See this example:

    julia> f(::Vector{Vector{<:Real}}) = "inner"
    f (generic function with 1 method)
    
    julia> f(::Vector{Vector{T}}) where {T<:Real}= "outer"
    f (generic function with 2 methods)
    
    julia> f(::Any) = "all else"
    f (generic function with 3 methods)
    
    julia> methods(f)
    # 3 methods for generic function "f":
    [1] f(::Array{Array{#s1,1} where #s1<:Real,1}) in Main at REPL[1]:1
    [2] f(::Array{Array{T,1},1}) where T<:Real in Main at REPL[2]:1
    [3] f(::Any) in Main at REPL[3]:1
    
    julia> f([[1]])
    "outer"
    
    julia> f(Vector{<:Real}[[1]])
    "inner"
    
    julia> f([Any[1]])
    "all else"
    

    With regards to the performance in normal (i.e. type stable) code Julia performs static method dispatch so this should not matter. Probably you could design a contrived example requiring a complex dynamic method dispatch or dynamic code generation where this might matter (but for practical reasons this should not be relevant - just use the specification that represents your needs).