samlerepo/2019/Julia/Code/10.structs1.jl

174 lines
6.1 KiB
Julia
Raw Normal View History

2019-09-12 16:32:16 +02:00
# User defined types are made with the struct keyword. Member variables are
# listed in the struct body as follows.
# Keep in mind that julia does not support redefining of types by default,
# so whenever you change anything in the struct (field names/types, etc.)
# you have to restart the REPL.
# Alternatively you could look into the package Revise.jl which solves
# some of these problems.
struct TestType
x
y
z
end
# Every struct gets a default constructor that is the typename taking
# in all the member variables in order.
t = TestType(1, 2, 3)
# Every struct also gets a default print function that shows the variable
# in style of the constructor.
@show t
# Accessing the member variables of the struct is done simply by just
# writing variable_name.field_name
# every field is always public as julia does not implement any heavy
# object oriented design.
@show t.x
# This line is illegal since struct are immutable by default.
# t.y = 5
# Field mutability can be achieved by adding the keyword "mutable" in front.
# However for simple structs, like this xyz-point like structure, it is
# much more efficient if you can avoid making it mutable. This has to do
# with how mutable struct are allocated differently then immutable ones.
# Read more in the "Types" section of the julia documentation.
mutable struct TestType2
x::Float64
y::Float64
z::Float64
end
# Also as illustrated above, the fields in a struct can be strictly typed.
# This is usually a good idea since it makes the struct take a constant
# amount of space, and removes a lot of type bookkeping.
t2 = TestType2(1.68, 3.14, 2.71)
# With this mutable struct, the variable now behaves more like a general
# container (Array/vector) so you can change the fields.
t2.y = 1.23
@show t2
# You can also make parametric types, kind of like templates in C++.
# This essentially creates a new type for each new T, so it alleviates the
# problem of keeping track of the types of the individual fields.
struct TestType3{T}
x::T
y::T
z::T
end
# This now constructs a TestType3{Int64}
t3 = TestType3(1, 2, 3)
@show t3
# This will be the example struct throughout the rest of the file.
struct Polar{T<:Real}
r::T
θ::T
# You might want to make your own constructors for a type.
# These are typically made inside the struct body since this overrides
# the creation of the default constructor described above.
function Polar(r::T, θ::T) where T <: Real
# Doing some constructy stuff
println("Creating Polar number (r = $r, θ = )")
# The actual variable is created by calling the "new()" function
# which acts as the default constructor. This however is just
# accessible to functions defined inside the struct body.
new{T}(r, θ)
end
# You might want to implement multiple different constructors.
# This one is short and simple so it can be created with the inline syntax.
# The zero(T) function finds the apropriate "zero" value
# for the given type T. That way no implicit conversions have to be done.
Polar{T}() where T <: Real = new{T}(zero(T), zero(T))
end
# Not all constructors have to be defined inside the struct body, but here
# outside the body we no longer have access to the "new()" function since
# the compiler doesn't have any way to infer which type "new" would refer
# to out here. So instead we have to use one of the constructors we defined
# inside the struct.
# This constructor is very similar to the Polar{T}() function but instead
# of writing p = Polar{Int}() to take the type in as a template argument
# you would pass the type in as an actual argument; p = Polar(Int)
Polar(::Type{T}) where T <: Real = Polar{T}()
# Constructing with the first constructor
p = Polar(3.14, 2.71)
@show p
# Constructing with the empty constructor
p = Polar{Int}()
@show p
# Constructing with the constructor taking the type as an argument
p = Polar(Float16)
@show p
# You might want to overload som basic operators and functions on your
# type. One common thing to want to override is how your variables are printed.
# Since there are so many different ways of converting a variable to text
# (print, println, @printf, string, @show, display, etc.)
# it can be difficult to find out which function is actually responsible
# for converting to text.
# This function turns out to be the Base.show() function.
# It takes in an IO stream object and and the variable you want to show.
# Since the funtion is from the Base module
# we have to override Base.show specifically
# Also it is very important to actually specify the type of the variable here
# so that we are actually specifying the show function for our type only
# and not for any general type.
function Base.show(io::IO, p::Polar)
# Say we want to print our polar number as r * e^(i θ) style string
show(io, p.r) # non strings we want to recursively show
write(io, "^(i ⋅ ") # strings we want to write directly with write
show(io, p.θ)
write(io, ")")
end
# Now our polar numbers are printed as we specified
p = Polar(3.14, 2.71)
@show p
println(p)
display(p)
# Even converting to a string is now done with our show functon
s = string(p)
@show s
# Overloading operators is even simpler as every infix operator is just a
# function; a + b is equivalent to +(a, b) (for all you lisp lovers)
# For some reason we cannot write the Base.* inline for infix operators
import Base.*
*(p1::Polar{T}, p2::Polar{T}) where T = Polar(p1.r * p2.r, p1.θ + p2.θ)
# Multiplication between different types might also be useful
*(x::T, p::Polar{T}) where T = Polar(x * p.r, p.θ)
# Implementing the reverse mulitplication such that it becomes commutative
*(p::Polar{T}, x::T) where T = x * p
p1 = Polar(2.0, 15.0)
p2 = Polar(3.0, 30.0)
p3 = p1 * p2
@show p3
# The in-place arithmetic operators (+=, -=, *=, /=, etc.) are not actual
# operators but rather just an alias for a = a * b so they need not be
# implemented seperately. If the memory copying is a problem and you
# really have to do it in-place the way to do that would be to make some
# sort of mult!(p, x) function.
p3 *= 0.5
@show p3