Types
Introduction
In Julia, every variable has a type. We can use the function typeof
(just like in R) to tell us the type of a variable. For example:
a = 1.0
typeof(a)
Float64
b = "Hello!"
typeof(b)
String
c = rand(10)
typeof(c)
Vector{Float64} (alias for Array{Float64, 1})
We see that, for example, the type of double-precision floating point numers is called Float64
in julia.
Abstract Types
However, in addition to the types a variable can have, there are so-called "abstract types". Abstract types "bundle" concrete types together, and form a "type hierarchy". Let's have a look:
typeof(a)
Float64
supertype(Float64)
AbstractFloat
subtypes(AbstractFloat)
4-element Vector{Any}:
BigFloat
Float16
Float32
Float64
The function supertype
allows us to inspect the next higher abstract type in the type hierarchy. In this case, this type is called AbstractFloat
. Calling subtypes
on AbstractFloat
, we see that Float64
is "bundled" together with some other types, for example single precision floating point numbers (Float32
). If we would explore this type hierarchy further, we could see something like this.
In Julia, we can easily define new abstract types:
abstract type MySpecialNumber <: Number end
defines a new abstract type MySpecialNumber
that is a subtype of Number
.
Composite types
The most import kind of type we will encounter during this workshop is called a "composite type". Composite types are also called "structs" and they allow us to create very useful objects. As an example, suppose we are writing a video game for the well-known Pokemon series. [1]
If you are not familiar with pokemon, don't worry. Pokemon are animal-like creatures from a video game series that fight against each other - every Pokemon has a certain type (Electric, Flying, Normal), and those types define how they interact. Here are pictures of some Pokemon we are using as examples:
That's all you need to know to complete the exercises.
We could define some abstract types
abstract type Pokemon end
abstract type Normal <: Pokemon end
abstract type Flying <: Pokemon end
abstract type Electric <: Pokemon end
and then a composite type
struct Pikachu <: Electric
nickname
attack
defense
speed
hp
end
We now have an abstract type Pokemon
with subtypes Normal
, Flying
and Electric
, and a composite type Pikachu
which is a subtype of Electric
. The composite type Pikachu
has the "fields" nickname
, attack
, defense
, speed
and hp
, where we can store the respective values.
We are now able to create our very own Pikachu
to fight in our team:
my_pikachu = Pikachu("Pika", 135, 80, 110, 132)
Main.Pikachu("Pika", 135, 80, 110, 132)
which creates a variable my_pikachu
of type Pikachu
. Note, that you may or may not see the prefix Main.
, this is nothing to worry about and merely an artifact of how this website is generated.
We can retrieve the values stored in the fields as
my_pikachu.defense
80
One thing to notice is that types cannot be re-defined in a running julia session. For example, trying to re-define the Pikachu
type will result in an error:
struct Pikachu <: Electric
nickname
attack
end
ERROR: invalid redefinition of constant Pikachu
In Julia, once a type is defined, it is locked in place due to the language's "just-in-time" compilation process. This feature enhances performance during normal use, but it can be a little cumbersome during a workshop where you are programming interactively. For example, you might want to try things out, or you made an error while defining a type. Unfortunately, correcting your error makes it necessary to restart Julia. Luckily, you did learn how to do that at the beginning of the workshop – remember that you can use the VSCode command palette.
Create a new composite type for a pokemon of your choice of type Flying
, create an instance of that pokemon, and retrieve it's nickname.
show solution
struct Crobat <: Flying
nickname
attack
defense
speed
hp
end
my_crobat = Crobat("Xwing", 105, 100, 210, 112)
my_crobat.nickname
Constructors
Let's talk about how we create new objects. In the example above, we called the type (Pikachu
, Crobat
) with the values we want to store in the respective fields (Pikachu("Pika", 135, 80, 110, 132)
) to create a new instance of that pokemon. However, we may like to have more convenience or safety. For this purpose, we have Constructors: functions that create new objects.
Outer Constructors
Outer constructors are mainly for convenience reasons, and we define them just like functions. For example, we may want to have the option of not giving a new Pokemon a nickname:
import Random: randstring
Pikachu(attack, defense, speed, hp) = Pikachu(randstring(10), attack, defense, speed, hp)
Main.Pikachu
So if we want to be lazy and not come up with a nickname, we can sample a random one:
my_lazy_pikachu = Pikachu(132, 34, 23, 343)
Main.Pikachu("lgCCR74tJ9", 132, 34, 23, 343)
Inner Constructors
Inner constructors can be used for enforcing that newly created objects obey certain rules. For example, the way we defined our Pikachu
type, there was nothing to tell Julia which kind of objects we actually can store in the fields. This allows us to do something like this:
weird_pikachu = Pikachu(132, 34, 23, -12)
Main.Pikachu("HIbkHg8zk5", 132, 34, 23, -12)
Of course, this is not a valid Pokemon, as the maximum health points can't be negative. To fix this, we use an inner constructor. This is just another function, but defined inside the type definition. Suppose we define another type of Pokemon like this:
struct Pichu <: Electric
nickname
attack
defense
speed
hp
function Pichu(nickname, attack, defense, speed, hp)
if (attack < 0) | (defense < 0) | (speed < 0) | (hp < 0)
error("Your Pokemon's stats are outside the valide range")
else
return new(nickname, attack, defense, speed, hp)
end
end
end
So we add a function to the type definition that has the same name as the type. This function checks whether the inputs are valid and throws an error if not. If they are valid, it uses the special new
function (which is only available inside type definitions) to create a new (hopefully valid) object.
Let's check if it works:
weird_pichu = Pichu("Pika_2.0", 132, 34, 23, -12)
Your Pokemon's stats are outside the valide range
- 1Inspired by https://gdalle.github.io/JuliaComputationSolutions/hw1a_solutions.html