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?

4 Likes

Note Integeris probably what you meant.

3 Likes

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.

4 Likes

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.

2 Likes

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.

2 Likes

I don’t think that’s true:

julia> Missing <: Missing <: Integer
false

julia> Missing <: Int <: Integer
false
1 Like

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
3 Likes