What are lower bounds for types?

I’m trying to understand what lower bounds are for types. I ran the following code which shows which subtypes (recursively) of number satisfies/rejects Int <: T <: Real in hope of gaining an understanding, however I’m still lost. :sleepy:

Number
β”œβ”€ Complex
└─ Real
   β”œβ”€ AbstractFloat
   β”‚  β”œβ”€ BigFloat
   β”‚  β”œβ”€ Float16
   β”‚  β”œβ”€ Float32
   β”‚  └─ Float64
   β”œβ”€ AbstractIrrational
   β”‚  └─ Irrational
   β”œβ”€ FixedPoint
   β”‚  β”œβ”€ Fixed
   β”‚  └─ Normed
   β”œβ”€ Integer
   β”‚  β”œβ”€ Bool
   β”‚  β”œβ”€ Signed
   β”‚  β”‚  β”œβ”€ BigInt
   β”‚  β”‚  β”œβ”€ Int128
   β”‚  β”‚  β”œβ”€ Int16
   β”‚  β”‚  β”œβ”€ Int32
   β”‚  β”‚  β”œβ”€ Int64
   β”‚  β”‚  └─ Int8
   β”‚  └─ Unsigned
   β”‚     β”œβ”€ UInt128
   β”‚     β”œβ”€ UInt16
   β”‚     β”œβ”€ UInt32
   β”‚     β”œβ”€ UInt64
   β”‚     └─ UInt8
   β”œβ”€ Rational
   └─ TestStat
function stype(input)
    arr = []
    append!(arr,subtypes(input))
    for x in arr
        z = subtypes(x)
        if (z != Type[])
            append!(arr,z)
        end
    end
    arr
end

for T in stype(Number)
    @show T, Int <: T <: Real
end
(T, Int <: T <: Real) = (Complex, false)
(T, Int <: T <: Real) = (Real, true)
(T, Int <: T <: Real) = (AbstractFloat, false)
(T, Int <: T <: Real) = (AbstractIrrational, false)
(T, Int <: T <: Real) = (FixedPointNumbers.FixedPoint, false)
(T, Int <: T <: Real) = (Integer, true)
(T, Int <: T <: Real) = (Rational, false)
(T, Int <: T <: Real) = (StatsBase.TestStat, false)
(T, Int <: T <: Real) = (BigFloat, false)
(T, Int <: T <: Real) = (Float16, false)
(T, Int <: T <: Real) = (Float32, false)
(T, Int <: T <: Real) = (Float64, false)
(T, Int <: T <: Real) = (Irrational, false)
(T, Int <: T <: Real) = (FixedPointNumbers.Fixed, false)
(T, Int <: T <: Real) = (FixedPointNumbers.Normed, false)
(T, Int <: T <: Real) = (Bool, false)
(T, Int <: T <: Real) = (Signed, true)
(T, Int <: T <: Real) = (Unsigned, false)
(T, Int <: T <: Real) = (BigInt, false)
(T, Int <: T <: Real) = (Int128, false)
(T, Int <: T <: Real) = (Int16, false)
(T, Int <: T <: Real) = (Int32, false)
(T, Int <: T <: Real) = (Int64, true)
(T, Int <: T <: Real) = (Int8, false)
(T, Int <: T <: Real) = (UInt128, false)
(T, Int <: T <: Real) = (UInt16, false)
(T, Int <: T <: Real) = (UInt32, false)
(T, Int <: T <: Real) = (UInt64, false)
(T, Int <: T <: Real) = (UInt8, false)

I don’t understand why Int <: T <: Real is only true for Real, Integer, Signed and Int64

Int is just an alias for Int64 on 64-bit platforms and Int32 on 32-bit platforms. Since you seem to be running a 64-bit Julia version, you get exactly the same results for Int as you get for Int64. Does that answer your question?

Note Integeris probably what you meant.

Yes, Integer is what I meant, however I still don’t understand what the lower bound means.

Integer <: Int64 <: Real
false

Why is this false? What does the lower bound mean?

Int64 (which is a concrete type) is a subtype of Integer (which is an abstract type). The reverse cannot be true.

This makes sense, thank you for the explanation, however when would you use an upper bound with a lower bound?

I never use them. I’ve seen them used, but cannot remember what for.

One example would be Missing<:T<: Integer which is a way of writing T<: Union{Missing, Integer}. In general, lower bounds aren’t widely used.

I don’t think that’s true:

julia> Missing <: Missing <: Integer
false

julia> Missing <: Int <: Integer
false

Lower bounds are sometimes useful though, for example if you want to check whether a vector can store missing values:

julia> [1, missing, 3] isa Vector{>:Missing}
true

julia> [1, 2, 3] isa Vector{>:Missing}
false