Julia type system

Last updated on 2023-08-29 | Edit this page

Overview

Questions

  • “What is the use of types?”
  • “How are types organized in Julia?”

Objectives

  • “Understand the structure of the type tree.”
  • “Know how to traverse the type tree.”
  • “Know how to build mutable and immutable types.”

Structuring variables


Melissa wants to keep the variables corresponding to the trebuchet (counterweight, release_angle) separate from the variables coming from the environment (wind, target_distance). That is why she chooses to group them together using structures. There are two structure types:

  • immutable structures, whose fields can not be changed after creation
  • keyword: struct
  • mutable structures, whose fields can change after creation
  • keyword: mutable struct

Since Melissa wants to change the parameters of the trebuchet, she uses a mutable struct for it. But she cannot influence the environment and thus uses a struct for those values.

JULIA

mutable struct Trebuchet
  counterweight::Float64
  release_angle::Float64
end

struct Environment
  wind::Float64
  target_distance::Float64
end

Types and hierarchy

Here ::Float64 is a type specification, indicating that this variable should be a 64-bit floating point number, and :: is an operator that is read “is an instance of.” If Melissa hadn’t specified the type, the variables would have the type Any by default.

In Julia every type can have only one supertype, so let’s count how many types are between Float64 and Any:

1.

JULIA

supertype(Float64)

OUTPUT

AbstractFloat

2.

JULIA

supertype(AbstractFloat)

OUTPUT

Real

3.

JULIA

supertype(Real)

OUTPUT

Number

4.

JULIA

supertype(Number)

OUTPUT

Any

So we have the relationship Float64 <: AbstractFloat <: Real <: Number <: Any where <: is the subtype operator, used here to mean the item on the left “is a subtype of” the item on the right.

Float64 is a concrete type, which means that you can actually create objects of this type. For example 1.0 is an object of type Float64. We can check this at the REPL using either (or both) the typeof function or the isa operator:

JULIA

typeof(1.0)

OUTPUT

Float64

or

JULIA

1.0 isa Float64

OUTPUT

true

All the other types are abstract types that are used to address groups of types. For example, if we declare a variable as a::Real then it can be bound to any value that is a subtype of Real.

Let’s quickly check what are all the subtypes of Real:

JULIA

subtypes(Real)

OUTPUT

4-element Vector{Any}:
 AbstractFloat
 AbstractIrrational
 Integer
 Rational

This way the types form a tree with abstract types on the nodes and concrete types as leaves. Have a look at this visualization of all subtypes of Number:

Is it Real?

For which of the following types T would the following return false?

JULIA

1.0 isa T
  1. Real
  2. Number
  3. Float64
  4. Integer

The correct answer is 4: while 1 is an integer, 1.0 is a floating-point value.

Instances


So far Melissa only defined the layout of her new types Trebuchet and Environment. To actually create a value of this type she has to call the so called constructor, which is a function with the same name as the corresponding type and as many arguments as there are fields.

JULIA

trebuchet = Trebuchet(500, 0.25pi)

OUTPUT

Trebuchet(500.0, 0.7853981633974483)

Note, how the values will get converted to the specified field type.

JULIA

environment = Environment(5, 100)

OUTPUT

Environment(5.0, 100.0)

trebuchet is being called an instance or object of the type Trebuchet. There can only ever be one definition of the type Trebuchet but you can create many instances of that type with different values for its fields.

Creating a subtype


A concrete type can be made a subtype of an abstract type with the subtype operator <:. Since Trebuchet contains several fields that are mutable Melissa thinks it is a good idea to make it a subtype of AbstractVector.

Caveat: Redefining Structs

JULIA

mutable struct Trebuchet <: AbstractVector{Float64}
  counterweight::Float64
  release_angle::Float64
end

ERROR

ERROR: invalid redefinition of constant Trebuchet
Stacktrace:
[1] top-level scope
   @ REPL[9]:1

This error message is clear: you’re not allowed to define a struct using a name that’s already in use.

Restart the REPL

In Julia it is not very easy to redefine structs. It is necessary to restart the REPL to define the new definition of Trebuchet, or take a different name instead.

Melissa decides to keep going and come back to this later.

Key Points

  • “In Julia types have only one direct supertype.”