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:
julia> x::Float64 = 8
8
julia> typeof(x)
Float64We 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.
. . .
julia> subtypes(Real)
4-element Vector{Any}:
AbstractFloat
AbstractIrrational
Integer
Rational
julia> supertype(Float64)
AbstractFloatFor 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.
julia> Float64 <: Any
true. . .
Concrete types such as Float64 or Int64 can be instantiated, whereas abstract types exist only in the type hierarchy.
julia> isconcretetype(Float64)
true
julia> isabstracttype(AbstractFloat)
true. . .
There are also composite types, which are made up of many smaller types.
struct Person
name::String
age::Int
married::Bool
end. . .
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.
julia> author = Person("Marcel", 29, false)
Person("Marcel", 29, false)
julia> typeof(author)
PersonAs usual, we can access the member variables of a composite type using the . notation.
julia> author.name
"Marcel"
julia> author.age
29
julia> author.married
false. . .
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
a::Int
b::Real
const c::Char
end
julia> X = Triple(8, 3.7, 'K')
Triple(8, 3.7, 'K')
julia> X.a = 5
5
julia> X.c = 'M'
ERROR: setfield!: const field .c of type Triple cannot be changed
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.
julia> typeof(Real)
DataType
julia> typeof(Person)
DataType
julia> typeof(DataType)
DataTypeType 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.
IntOrString = Union{Int, AbstractString}
x = 8::IntOrString
x = "Hello!"
println(x)using IntOrString = std::variant<int, std::string>;
auto x = IntOrString(8);
x = "Hello!";
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}
x::T
y::T
end
P = Point{Float64}(5, 8)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:
julia> Point{Float64} <: Point{Real}
falseIn 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)
endSince 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)
endAlternatively, 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
num::T
den::T
endTuples
sdf
TODO:
- Julias Types System, Multiple Dispatch
- Functional Programming (map, reduce; Beispiel FP vs OOP, normalverteilung)
- Arrays
- Sets
- Dictionaries