Need for explicitly defining subtypes in function arguments?

Regarding this part of the question specifically, note that Julia specializes code for the given argument types when compiling the function.

Indeed if this is the case then it seems to extend the generalization of subtypes to collections of subtypes would seem intuitive, and apparently possible in other languages - but I see now there is a different philosophy/justification for type invariance.

But it is very much possible in julia too, by typing e.g. Vector{<:Real}
It’s just, that the default is invariant und you have to request other behaviours explicitly.

1 Like

I suppose it is that simple in the end - just not obvious at first.

2 Likes

If you declare an argument type to be Vector{Real}, you are asserting that the argument must be a vector that can hold Float64 and Int64 and Rational and any other subtype of Real. Here’s an example of a function that actually requires the argument to be of type Vector{Real} in order to work properly:

function foo(v::Vector{Real})
    for x in v
        if x isa Int
            println("Found an Int.")
        elseif x isa Float64
            println("Found a Float64.")
        else
            println("Found a different subtype of Real.")
        end
    end
end
julia> x = Real[1, 2.3, 3//4]
3-element Vector{Real}:
  1
  2.3
 3//4

julia> foo(x)
Found an Int.
Found a Float64.
Found a different subtype of Real.
5 Likes

I’m taking about what a Vector{Real} is:

  • A Vector{Real} must be able to hold a float value.
  • A Vector{Int} cannot hold a float value.
  • Ergo, a Vector{Int} is not a Vector{Real}.
  • Equivalently, a Vector{Int} is not a subtype of Vector{Real}.

Additionally, a Vector{Real} is a concrete type. Concrete types cannot have subtypes.

4 Likes

The difference is not in the initial content of the vector, but in what can be put into it by the function. Suppose the function accepts Vector{Real}, does push!(v, 2.5) and you passed Vector{Int} – the function will fail unexpectedly. That’s why it is prohibited.

1 Like

Note, yyou don’t need to define subtypes (or any types) in function definitions (but you want to for structs, for speed), e.g. not:

g(x::Array{Real}) = x .* 2

You can just do e.g.:

g(x) = x .* 2

and it’s NEVER slower, nor “wrong”. But it allows more than you might think, and that’s ok (and for something that doesn’t make sense, means a runtime error):

julia> pow2(x) = x ^ 2

julia> pow2("Palli")
"PalliPalli"

If you want to be specific you want some type of array, document that way for your users, then it’s neither wrong, but then you may want to be as general as possible, i.e. not just allow Aarry, but any subtype of AbstractArray, and likely containing any type (or maybe of just some).

2 Likes

Since noone seems to have linked it so far, there’s also this explanation by @StefanKarpinski (though with a slight Rust bend, as that was the original question there):

3 Likes

Thanks - this does answer my original question - why you want that the subtypes not be allowed on some cases.

1 Like

Super informative thread. I shared the same assumptions as the original poster.

3 Likes

Indeed the heterogenous array case I also failed to think about, but would be a case where subtypes should not implicitly allowed. Thanks - was informative.

1 Like