Julias Type System
Last week we started programming with Julia, covering basic control flow, functions and strings. In this video we will learn about Julias type system and a very powerful mechanism called “multiple dispatch”. We will also talk about important data structures such as arrays, sets and dictionaries.
julia, programming
Types in Julia
There are two types of programming languages: Statically typed systems such as C++, where each variable must be of a particular type before execution, and dynamically typed systems, where the type is not known until runtime. Julia is a dynamically typed language, but still has the ability to specify certain types for better efficiency.
. . .
Recall that you can specify the type of a variable either by calling its constructor, or via using the :: operator:
> x::Float64 = 8
julia8
> typeof(x)
juliaFloat64
We can determine the type of a variable with the typeof()
function.
Types in Julia are organised in a hierarchy, which is very similar to inheritance in object-oriented languages such as C++, except that it also works for primitive types. Each type has exactly one parent type and possibly several child types, which can be determined using the supertype
and subtype
cmmands.
. . .
> subtypes(Real)
julia4-element Vector{Any}:
AbstractFloat
AbstractIrrational
Integer
Rational
> supertype(Float64)
juliaAbstractFloat
For example, Real
is an abstract type representing real numbers, which has subtypes for rational, integer, and floating-point types.
This way we can display the complete type tree:
Figure was created with app.diagrams.net and is hereby licensed under Public Domain (CC0)
As you can see, each type is a subtype of the type Any
. We can check whether a type is a subtype of another using the <: operator.
> Float64 <: Any
juliatrue
. . .
Concrete types such as Float64 or Int64 can be instantiated, whereas abstract types exist only in the type hierarchy.
> isconcretetype(Float64)
juliatrue
> isabstracttype(AbstractFloat)
juliatrue
. . .
There are also composite types, which are made up of many smaller types.
struct Person
::String
name::Int
age::Bool
marriedend
. . .
Composite types in Julia are not the same as classes in other languages. They don’t support inheritance and can’t have member functions.
. . .
To instantiate a variable of that type, we call it’s constructor.
> author = Person("Marcel", 29, false)
juliaPerson("Marcel", 29, false)
> typeof(author)
julia Person
As usual, we can access the member variables of a composite type using the . notation.
> author.name
julia"Marcel"
> author.age
julia29
> author.married
juliafalse
. . .
By default, composite types are immutable, meaning they cannot be changed. However, an immutable object can contain mutable fields, such as arrays, which remain mutable.
To define a mutable type, use the mutable
keyword. If you want to ensure that a particular field remains constant in an otherwise mutable object, you can do this using the const
keyword.
mutable struct Triple
::Int
a::Real
bconst c::Char
end
> X = Triple(8, 3.7, 'K')
juliaTriple(8, 3.7, 'K')
> X.a = 5
julia5
> X.c = 'M'
julia: setfield!: const field .c of type Triple cannot be changed
ERROR:
Stacktrace...] [
TODO: Ist das wichtig?
Abstract, primitive and composite types are all instances of the same concept, DataType
, which is the type of these types.
> typeof(Real)
julia
DataType
> typeof(Person)
julia
DataType
> typeof(DataType)
julia DataType
Type Unions
What if you want to specify that a function accepts signed and unsigned integers, but not bool? You can use a union type.
The concept is similar in other programming languages.
= Union{Int, AbstractString}
IntOrString
= 8::IntOrString
x = "Hello!"
x
println(x)
using IntOrString = std::variant<int, std::string>;
auto x = IntOrString(8);
= "Hello!";
x
std::println(std::get<std::string>(x));
A particularly useful case of a Union type is Union{T, Nothing}
, which would be equivalent to std::optional
in C++.
Parametric Types
Types in Julia can take parameters, so type declarations introduce a whole family of types. This concept is known in other programming languages as generic programming.
struct Point{T}
::T
x::T
yend
= Point{Float64}(5, 8) P
template <typename T>
{
stuct Point ;
T x;
T y}
auto P = Point<double>(5, 8)
Note that although Float64
is a subtype of Real
, we do NOT have:
> Point{Float64} <: Point{Real}
juliafalse
In other words, Julia’s type parameters are invariant.
. . .
Let’s say we want to write a generic function that can take Point{Float64}
as an argument. The following method won’t work:
function norm(p::Point{Real})
sqrt(p.x^2 + p.y^2)
end
Since Point{Float64}
is not a subtype of Point{Real}
, the function can’t take Point{Float64}
as an argument.
. . .
The correct way to define a method that accepts all arguments of type Point{T}
where T
is a subtype of Real
is:
function norm(p::Point{<:Real})
sqrt(p.x^2 + p.y^2)
end
Alternatively, one could also write
function norm(p::Point{T}) where T<:Real;
sqrt(p.x^2 + p.y^2)
end
. . .
Implement a parametric type for rational numbers.
struct Rational{T<:Integer} <: Real
::T
num::T
denend
Tuples
sdf
TODO:
- Julias Types System, Multiple Dispatch
- Functional Programming (map, reduce; Beispiel FP vs OOP, normalverteilung)
- Arrays
- Sets
- Dictionaries