5 - Custom structures

Structures (previously known in Julia as "Types") are, for the most (see later for the difference), what in other languages are called classes, or "structured data": they define the kind of information that is embedded in the structure, that is a set of fields (aka "properties" in other languages), and then individual instances (or "objects") can be produced each with its own specific values for the fields defined by the structure.

They are "composite" types, in the sense that are not made of just a fixed amound of bits as instead "primitive" types.

Some syntax that will be used in the examples:

  • a::B means "a must be of type B"

  • A<:B means "A must be a subtype of B".

Defining a structure

mutable struct MyOwnType

For increasing performances in certain circumstances, you can optionally specify the type of each field, as done in the previous example for property2.

You can use templates also in structure declaration:

mutable struct MyOwnType{T<:Number}

You can omit the mutable keyword in front of struct when you want to enforce that once an object of that type has been created, its fields can no longer be changed (i.e. , structures are immutable by default. Note that mutable objects -as arrays- remain themselves mutable also in a immutable structure). Although obviously less flexible, immutable structures are much faster.

You can create abstract types using the keyword abstract type. Abstract types do not have any field, and objects can not be instantiated from them, although concrete types (structures) can be defined as subtypes of them (an issue to allow abstract classes to have fields is currently open and may be implemented in the future).

Actually you can create a whole hierarchy of abstract types:

abstract type MyOwnGenericAbstractType end
abstract type MyOwnAbstractType <: MyOwnGenericAbstractType end
mutable struct AConcreteType <: MyOwnAbstractType

Initialising an object and accessing its fields

myObject = MyOwnType("something","something",10)
a = myObject.property3 # 10

Note that you initialise the object with the values in the order that has been specified in the structure definition.

Implementation of the OO paradigm in Julia

Let's take the following example:

struct Person
struct Shoes
struct Student
function printMyActivity(self::Student)
println("I study at $(self.school) school")
struct Employee
function printMyActivity(self::Employee)
println("I work at $(self.company) company")
gymShoes = Shoes("gym","white")
proShoes = Shoes("classical","brown")
Marc = Student(Person("Marc",15),"Divine School",gymShoes)
MrBrown = Employee(Person("Brown",45),1200.0,"ABC Corporation Inc.", proShoes)

There are three big elements that distinguish Julia implementation from a pure Object-Oriented paradigm:

  1. Firstly, in Julia you do not associate functions to a type. So, you do not call a function over a method (myobj.func(x,y)) but rather you pass the object as a parameter (func(myobj, x, y));

  2. In order to extend the behaviour of any object, Julia doesn't use inheritance (only abstract classes can be inherited) but rather composition (a field of the subtype is of the higher type, allowing access to its fields). I personally believe that this is a bit a limit in the expressiveness of the language, as the code can not consider directly different concepts of relations between objects (e.g. Person->Student specialisation, Person->Arm composition, Person->Shoes weak relation );

  3. Multiple-inheritance is not supported (yet).

More on types

Some useful type-related functions:

  1. supertype(MyType)Returns the parent types of a type

  2. subtypes(MyType) Lists all children of a type

  3. fieldnames(MyType) Queries all the fields of a structure

  4. isa(obj,MyType) Checks if obj is of type MyType

  5. typeof(obj) Returns the type of obj

This is the complete type hierarchy of Number in Julia (credits to Wikipedia):

Note that because concrete types are definitive, i.e. that can't have further subtypes, arrays of ConcreteType are not subtypes of arrays of AbstractType, even when ConcreteType is actually a subtype of AbstractType, This is true for any parametric type, not just arrays. In function signature, when you want to express the concept that you want to accept as argument an array of any subtype of AbstractType, you can use a template.

While an updated, expanded and revised version of this chapter is available in "Chapter 4 - Custom Types" of Antonello Lobianco (2019), "Julia Quick Syntax Reference", Apress, this tutorial remains in active development.