Skip to content
AgileLogics
6 min read

The Type System and Type Hierarchy

Julia's types form a tree. At the top is Any. Inner nodes are abstract types — categories like Number or AbstractString — and the leaves are concrete types like Int64 and String, which actual values have.

using InteractiveUtils   # subtypes (loaded automatically in the REPL)

println(supertype(Int64))
println(supertype(Signed))
println(supertype(Integer))
println(supertype(Real))
println(supertype(Number))

println(subtypes(Integer))
println(Int64 <: Integer, " ", Int64 <: AbstractFloat, " ", Float64 <: Real)
println(isabstracttype(Number), " ", isconcretetype(Float64))
Signed
Integer
Real
Number
Any
Any[Bool, Signed, Unsigned]
true false true
true true
  • supertype(T) walks up the tree; subtypes(T) lists the children. subtypes lives in the InteractiveUtils standard library, which the REPL loads for you; scripts need using InteractiveUtils.
  • A <: B means "A is a subtype of B".
  • You can't create a value of an abstract type — there's no plain "Number" value, only Int64s, Float64s, and so on.

The whole path

function ancestors(T)
    path = [T]
    while T !== Any
        T = supertype(T)
        push!(path, T)
    end
    return join(path, " <: ")
end

println(ancestors(Int8))
println(ancestors(Float32))
println(ancestors(String))
println(ancestors(Vector{Int}))
Int8 <: Signed <: Integer <: Real <: Number <: Any
Float32 <: AbstractFloat <: Real <: Number <: Any
String <: AbstractString <: Any
Vector{Int64} <: DenseVector{Int64} <: AbstractVector{Int64} <: Any

Type annotations

:: asserts or declares a type:

x = (1 + 2)::Int
println(x)
try
    (1 + 2.0)::Int
catch err
    println(sprint(showerror, err))
end

function half(n::Integer)
    return n ÷ 2
end
println(half(9))
try
    half(9.0)
catch err
    println(typeof(err))
end
3
TypeError: in typeassert, expected Int64, got a value of type Float64
4
MethodError
  • On an expression, ::T checks the value's type and throws if it's wrong.
  • On a function argument, ::T restricts which values the function accepts. Using an abstract type like Integer keeps the function general — it works for Int8, Int64, BigInt, and more.

Union types and Nothing

function find_user(id)::Union{String,Nothing}
    users = Dict(1 => "ada", 2 => "alan")
    return get(users, id, nothing)
end

println(find_user(1))
println(find_user(9) === nothing)
println(Union{Int,String} >: Int)
println(typeof(nothing), " ", Nothing <: Any)
ada
true
true
Nothing true

Union{A,B} is a type whose values are either A or B. Union{T,Nothing} is the idiomatic "maybe a value" type.

Types are values

Types themselves are ordinary values you can store, pass, and compare — typeof(Int) is DataType. That's what makes the next lesson, multiple dispatch, possible.