Matrix{Float64}<:Matrix{Number} is false?

Why is Matrix{Float64}<:Matrix{Number} false?

search type invariance. TLDR is that they need different memory representations.

2 Likes

It also follows from the rules of concrete subtyping. Matrix{Number} is a concrete type that can be instantiated, and concrete types cannot have subtypes.

2 Likes

Try this:

Matrix{Float64} <: Matrix{<:Number}
1 Like

See also Types · The Julia Language

In particular:

In other words, in the parlance of type theory, Julia’s type parameters are invariant, rather than being covariant (or even contravariant). This is for practical reasons: while any instance of Point{Float64} may conceptually be like an instance of Point{Real} as well, the two types have different representations in memory:

  • An instance of Point{Float64} can be represented compactly and efficiently as an immediate pair of 64-bit values;
  • An instance of Point{Real} must be able to hold any pair of instances of Real. Since objects that are instances of Real can be of arbitrary size and structure, in practice an instance of Point{Real} must be represented as a pair of pointers to individually allocated Real objects.

The efficiency gained by being able to store Point{Float64} objects with immediate values is magnified enormously in the case of arrays: an Array{Float64} can be stored as a contiguous memory block of 64-bit floating-point values, whereas an Array{Real} must be an array of pointers to individually allocated Real objects – which may well be boxed 64-bit floating-point values, but also might be arbitrarily large, complex objects, which are declared to be implementations of the Real abstract type.

3 Likes

https://m3g.github.io/JuliaNotes.jl/stable/typevariance/

Could you elaborate on this a bit more? I am confused by the syntax of Matrix{<:Number}

Invariance of type parameters means that e.g. Matrix{Float64} is not a subtype of Matrix{Number}, even if Float64 is a subtype of Number. However, there exists “wildcard” type parameters in the form of UnionAll types. Typically such a UnionAll type is written with the where clause: Matrix{T} where T<:Number. It is an abstract type which has all Matrix{T} as subtypes where T is a subtype of Number. A short form is Matrix{<:Number}, which can be used if you do not need to use the type variable T elsewhere.

julia> dump(Matrix{<:Number})
UnionAll
  var: TypeVar
    name: Symbol #s3
    lb: Union{}
    ub: Number <: Any
  body: Array{var"#s3"<:Number, 2} <: DenseArray{var"#s3"<:Number, 2}

julia> dump(Matrix{T} where T<:Number)
UnionAll
  var: TypeVar
    name: Symbol T
    lb: Union{}
    ub: Number <: Any
  body: Array{T<:Number, 2} <: DenseArray{T<:Number, 2}
1 Like

Beat me to it :wink:

This was going to be my response:

As others have mentioned, Matrix{Number} is a concrete type, and one concrete type cannot subtype another concrete type.

Matrix{<:Number} is a union of types, and is shorthand for Matrix{T} where {T<:Number}.