01 JULIA1 - 5A: Types of types, composite types (17:23)
01 JULIA1 - 5B: Parametric types (7:37)
01 JULIA1 - 5C: Inheritance and composition OO paradigms (14:28)
0105 Custom Types
Some stuff to set-up the environment..
julia> cd(@__DIR__)julia> using Pkgjulia> Pkg.activate(".") # If using a Julia version different than 1.10 please uncomment and run the following line (reproductibility guarantee will however be lost) # Pkg.resolve() # Pkg.instantiate() # run this if you didn't in Segment 01.01Activating project at `~/work/SPMLJ/SPMLJ/buildedDoc/01_-_JULIA1_-_Basic_Julia_programming`julia> using Randomjulia> Random.seed!(123)Random.TaskLocalRNG()julia> using InteractiveUtils # loaded automatically when working... interactively
"Type" of types
In Julia primitive, composite and abstract types can all be defined by the user
julia> primitive type APrimitiveType 819200 end # name and size in bit - multiple of 8 and below 8388608 (1MB)julia> primitive type APrimitiveType2 819200 endjulia> 819200/(8*1024)100.0julia> struct ACompositeType end # fields, constructors.. we'll see this later in detailsjulia> abstract type AnAbstractType end # no objects, no instantialisation of objects
Composite types
julia> mutable struct Foo # starting with a capital letter field1 field2::String field3::ACompositeType end
mutable struct Foo # error! can't change a struct after I defined it end
julia> fieldnames(Foo)(:field1, :field2, :field3)julia> o = Foo(123,"aaa",ACompositeType()) # call the default constructor (available automatically) - order matters!Main.var"Main".Foo(123, "aaa", Main.var"Main".ACompositeType())julia> typeof(o)Main.var"Main".Foo
Outer constructor
julia> function Foo(f2,f3=ACompositeType()) # "normal" functions just happens it has the name of the object to create if startswith(f2,"Booo") # put whatever logic you wish return nothing end return Foo(123,f2,f3) # call the default constructor endMain.var"Main".Foojulia> o = Foo(123,"aaa", ACompositeType()) # call the default constructorMain.var"Main".Foo(123, "aaa", Main.var"Main".ACompositeType())julia> o = Foo("blaaaaa") # call the outer constructor we definedMain.var"Main".Foo(123, "blaaaaa", Main.var"Main".ACompositeType())julia> o.field1 # access fields123julia> o.field1 = 321 # modify field (because type defined as "mutable" !!!)321julia> oMain.var"Main".Foo(321, "blaaaaa", Main.var"Main".ACompositeType())julia> function Base.show(io::IO, x::Foo) # function(o) rather than o.function() println(io,"My custom representation for Foo objects") println(io,"Field1: $(o.field1)") println(io,"Field2: $(o.field2)") endjulia> oMy custom representation for Foo objects Field1: 321 Field2: blaaaaa
Inner constructor
julia> mutable struct Foo2 field1::Int64 field2::String function Foo2(f1,f2,f3) # ... logic return new(f1+f2,f3) end end
If any inner constructor method is defined, no default constructor method is provided.
julia> Foo2(1,2,"aaa") # Foo2(1,"aaa") # Error, no default constructor !Main.var"Main".Foo2(3, "aaa")
You can also use a macro (requires Julia 1.1) to automatically define an (outer) keyword_based constructor with support for optional arguments:
julia> Base.@kwdef struct Kfoo x::Int64 = 1 y = 2 z endMain.var"Main".Kfoojulia> Kfoo(z=3)Main.var"Main".Kfoo(1, 2, 3)
Note that, at time of writing, @kwdef is not exported by Julia base, meaning that while widely used, it is considered still "experimental" and its usage may change in future Julia minor versions.
Custom pretty-printing
We can customise the way our custom type is rendered by overriding the Base.show function for our specific type.
We first need to import Base.show
julia> import Base.showjulia> mutable struct FooPoint x::Int64 y::Int64 endjulia> function show(io::IO, ::MIME"text/plain", x::FooPoint) # if get(io, :compact, true) ... # we can query the characteristics of the output print(io,"A FooPoint struct") endshow (generic function with 964 methods)julia> function show(io::IO, x::FooPoint) # overridden by print print(io, "FooPoint x is $(x.x) and y is $(x.y) \nThat's all!") endshow (generic function with 965 methods)julia> foo_obj=FooPoint(1,2)A FooPoint structjulia> display(foo_obj)julia> show(foo_obj)FooPoint x is 1 and y is 2 That's all!julia> print(foo_obj)FooPoint x is 1 and y is 2 That's all!julia> println(foo_obj)FooPoint x is 1 and y is 2 That's all!
Parametric types
julia> struct Point{T<:Number} # T must be a child of type "Number" x::T y::T endjulia> o = Point(1,2)Main.var"Main".Point{Int64}(1, 2)julia> Point(1.0,2.) # Point(1,2.0) # error !Main.var"Main".Point{Float64}(1.0, 2.0)julia> function Point(x::T, y::T=zero(T)) where {T} return Point(x,y) endMain.var"Main".Pointjulia> Point(2)Main.var"Main".Point{Int64}(2, 0)julia> Point(1.5)Main.var"Main".Point{Float64}(1.5, 0.0)julia> abstract type Figure{T<:Number} endjulia> a = Array{Int64,2}(undef,2,2) # Array is nothing else than a parametric type with 2 parameters2×2 Matrix{Int64}: 8 25 19 36julia> typeof(a)Matrix{Int64} (alias for Array{Int64, 2})julia> eltype(a)Int64
As we see for arrays, parameters doesn't need to be types, but can be any value of a bits type (in practice an integer value) :
julia> struct MyType{T,N} data::Array{T,N} endjulia> intMatrixInside = MyType([1 2 3; 4 5 6])Main.var"Main".MyType{Int64, 2}([1 2 3; 4 5 6])julia> floatVectorInside = MyType([1 2 3])Main.var"Main".MyType{Int64, 2}([1 2 3])julia> function getPlane(o::MyType{T,N},dim,pos) where {T,N} sizes = size(o.data) if length(sizes) > N error("Dim over the dimensions of the data") elseif sizes[dim] < pos error("Non enought elements in dimension $dim to cut at $pos") end return selectdim(o.data,dim,pos) endgetPlane (generic function with 1 method)julia> getPlane(intMatrixInside,1,2)3-element view(::Matrix{Int64}, 2, :) with eltype Int64: 4 5 6
A package where non-type parameters are emploied to boost speed is StaticArray.jl where one parameter is the size of the array that hence become known at compile time
Inheritance
julia> abstract type MyOwnGenericAbstractType end # the highest-leveljulia> abstract type MyOwnAbstractType1 <: MyOwnGenericAbstractType end # child of MyOwnGenericAbstractTypejulia> abstract type MyOwnAbstractType2 <: MyOwnGenericAbstractType end # also child of MyOwnGenericAbstractTypejulia> mutable struct AConcreteTypeA <: MyOwnAbstractType1 f1::Int64 f2::Int64 endjulia> mutable struct AConcreteTypeB <: MyOwnAbstractType1 f1::Float64 endjulia> mutable struct AConcreteTypeZ <: MyOwnAbstractType2 f1::String endjulia> oA = AConcreteTypeA(2,10)Main.var"Main".AConcreteTypeA(2, 10)julia> oB = AConcreteTypeB(1.5)Main.var"Main".AConcreteTypeB(1.5)julia> oZ = AConcreteTypeZ("aa")Main.var"Main".AConcreteTypeZ("aa")julia> supertype(AConcreteTypeA)Main.var"Main".MyOwnAbstractType1julia> subtypes(MyOwnAbstractType1)Any[]
When multiple methods are available for an object, function calls are dispatched to the most stricter method, i.e. the one defined over the exact parameter's type or their immediate parents
julia> function foo(a :: MyOwnGenericAbstractType) # good for everyone println("Default implementation: $(a.f1)") endfoo (generic function with 1 method)julia> foo(oA) # Default implementation: 2Default implementation: 2julia> foo(oB) # Default implementation: 1.5Default implementation: 1.5julia> foo(oZ) # Default implementation: aaDefault implementation: aajulia> function foo(a :: MyOwnAbstractType1) # specialisation for MyOwnAbstractType1 println("A more specialised implementation: $(a.f1*4)") endfoo (generic function with 2 methods)julia> foo(oA) # A more specialised implementation: 8A more specialised implementation: 8julia> foo(oB) # A more specialised implementation: 6.0A more specialised implementation: 6.0julia> foo(oZ) # Default implementation: aa # doesn't match the specialisation, default to foo(a :: MyOwnGenericAbstractType)Default implementation: aajulia> function foo(a :: AConcreteTypeA) println("A even more specialised implementation: $(a.f1 + a.f2)") endfoo (generic function with 3 methods)julia> foo(oA) # A even more specialised implementation: 12A even more specialised implementation: 12julia> foo(oB) # A more specialised implementation: 6.0A more specialised implementation: 6.0julia> foo(oZ) # Default implementation: aaDefault implementation: aa
Attention to the inheritance for parametric types. If it is true that Vector{Int64} <: AbstractVector{Int64} and Int64 <: Number, it is FALSE that AbstractVector{Int64} <: AbstractVector{Number}. If you want to allow a function parameter to be a vector of numbers, use instead templates explicitly, e.g. foo(x::AbstractVector{T}) where {T<:Number} = return sum(x)
julia> Vector{Int64} <: AbstractVector{Int64}truejulia> Int64 <: Numbertruejulia> Vector{Int64} <: Vector{Number}falsejulia> AbstractVector{Int64} <: AbstractVector{Number}false
Object-oriented model
OO model based on composition
julia> struct Shoes shoesType::String colour::String endjulia> struct Person myname::String age::Int64 endjulia> struct Student p::Person # by referencing a `Person`` object, we do not need to repeat its fields school::String shoes::Shoes # same for `shoes` endjulia> struct Employee p::Person monthlyIncomes::Float64 company::String shoes::Shoes endjulia> gymShoes = Shoes("gym","white")Main.var"Main".Shoes("gym", "white")julia> proShoes = Shoes("classical","brown")Main.var"Main".Shoes("classical", "brown")julia> Marc = Student(Person("Marc",15),"Divine School",gymShoes)Main.var"Main".Student(Main.var"Main".Person("Marc", 15), "Divine School", Main.var"Main".Shoes("gym", "white"))julia> MrBrown = Employee(Person("Brown",45),3200.0,"ABC Corporation Inc.", proShoes)Main.var"Main".Employee(Main.var"Main".Person("Brown", 45), 3200.0, "ABC Corporation Inc.", Main.var"Main".Shoes("classical", "brown"))julia> function printMyActivity(self::Student) println("Hi! I am $(self.p.myname), I study at $(self.school) school, and I wear $(self.shoes.colour) shoes") # I can use the dot operator chained... endprintMyActivity (generic function with 1 method)julia> function printMyActivity(self::Employee) println("Good day. My name is $(self.p.myname), I work at $(self.company) company and I wear $(self.shoes.colour) shoes") endprintMyActivity (generic function with 2 methods)julia> printMyActivity(Marc) # Hi! I am Marc, ...Hi! I am Marc, I study at Divine School school, and I wear white shoesjulia> printMyActivity(MrBrown) # Good day. My name is MrBrown, ...Good day. My name is Brown, I work at ABC Corporation Inc. company and I wear brown shoes
OO models based on Specialisation (Person → Student) or Weack Relation (Person → Shoes) instead of Composition (Person → Arm) can be implemented using third party packages, like e.g. SimpleTraits.jl or OOPMacro.jl
This page was generated using Literate.jl.