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)Float64b = "Hello!"
typeof(b)Stringc = 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)Float64supertype(Float64)AbstractFloatsubtypes(AbstractFloat)4-element Vector{Any}:
BigFloat
Float16
Float32
Float64The 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 enddefines 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 endand then a composite type
struct Pikachu <: Electric
nickname
attack
defense
speed
hp
endWe 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.defense80One 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
endERROR: invalid redefinition of constant PikachuIn 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.nicknameConstructors
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.PikachuSo 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
endSo 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