Sitemap

Object-Oriented programming in julia

4 min readJun 4, 2023
Press enter or click to view image in full size

Object-Oriented Programming(OOP) as seen in traditional languages like C++, Java and Python is centered around the concept of “objects” which are instances of classes that enscapsulate data and methods operation on that data

However in Julia, Oriented programming is not the primary focus, instead it emphasizes multiple dispatches, a more general concept. Despite this, OOP can still be achieved in Julia through the use of composite types and methods.

Implementing Classes using Composite TypesIn Julia, we can mimic the behaviour of traditional classes by using composite types, also known as structs. These composite types can have fields to store data, similar to instance variables in other programming languages. To define a composite type, use the `struct` keyword, followed by the name of the type and its fields.

Defining a Struct

struct Horse
name::String
age::Int
color::String
breed::String
end

Julia, while not a traditional OOP language, provides similar features but with some key differences:

Multiple Dispatch, not Method Dispatch: In Julia, functions are not owned by objects (like methods in OOP).

What is OOP?

OOP is a programming paradigm based on the concept of “objects”, which are entities that contain both data (attributes) and functions (methods) that operate on the data. This design promotes better organization and reusability of code, making it easier to design, maintain, and develop large-scale applications.

Pillars of OOP

  • Encapsulation — In Julia, data and functions are not encapsulated within objects. Data is defined in structs, and functions are defined separately. In Julia, functions are not owned by objects (like methods in OOP). Instead, Julia uses a paradigm called multiple dispatch, where functions are defined for specific combinations of arguments types, and the correct version of the function is called based on the types of all arguments. This is a more generalized form of polymorphism.
struct Dog
name::String
age::Int
end

fido = Dog("Fido", 5)
  • Inheritance — Julia has abstract types and composite types (structs) instead of classes. You can create hierarchies with abstract types and subtypes, but this is more a way of categorizing types for dispatch rather than creating subclasses that inherit properties and methods.
abstract type Animal end

struct Dog <: Animal
name::String
age::Int
end

struct Cat <: Animal
name::String
age::Int
end
  • Polymorphism — Instead of classes with methods, Julia uses generic functions and multiple dispatch to achieve polymorphism. A generic function is a collection of function definitions, or methods, that the language’s dispatch mechanism selects based on the types of all the function’s arguments.
bark(d::Dog) = println("Woof! I'm $(d.name) and I'm $(d.age) years old.")
meow(c::Cat) = println("Meow! I'm $(c.name) and I'm $(c.age) years old.")

sound(a::Animal) = "Some sound"
sound(a::Dog) = bark(a)
sound(a::Cat) = meow(a)

Base.@kwdef

The Base.@kwdef macro in Julia simplifies the definition of a type (either a mutable struct or an immutable struct) that has default values for its fields. This macro generates default constructors for the struct.

Base.@kwdef struct CommonAnimalProperties
name::String
age::Int
color::String
end

Constructors for Object creation

Given,

struct Point
x::Float64
y::Float64
end

Constructors are special functions that create new instances of composite types. When a new composite type is declared, Julia automatically provides a default constructor that accepts arguments for each field in the order they were declared. In the example above, you can create a new Point with p = Point(1.0, 2.0).

However, you can also define your own constructors for composite types. These can be defined inside the type declaration (inner constructors), or outside it (outer constructors).

Base.@kwdef struct CommonAnimalProperties
name::String
age::Int
color::String
end

abstract type Animal end

struct Dog <: Animal
common::CommonAnimalProperties
breed::String

function Dog(; name::String, age::Int, color::String, breed::String)
return new(CommonAnimalProperties(name, age, color), breed)
end
end

struct Cat <: Animal
common::CommonAnimalProperties
breed::String

Cat(; name::String, age::Int, color::String, breed::String) = new(CommonAnimalProperties(name, age, color), breed)
end

Here, we defined inner constructors for `Dog` and `Cat` with named parameters, which allow for more flexibility when creating instances of these types. For example, you can create a new `Dog` instance with

d = Dog(name="Buddy", age=3, color="black", breed="Labrador")

c = Cat(name="Fluffy", age=2, color="white", breed="persian")

both dog and cat constructors have equivalent functionality and they are just syntactically different

Inner vs Outer Custructors

Inner constructors are defined inside the block of a type declaration. They have access to the type’s fields and can enforce certain invariants or constraints. e.g.

struct PositiveNumber
x::Int

PositiveNumber(x) = x > 0 ? new(x) : throw(ArgumentError("Value must be positive"))
end

Outer constructors are defined outside the type declaration block. They can provide additional convenience methods for creating new objects, and they can call other constructors, but they cannot enforce constraints directly.

struct Point
x::Float64
y::Float64
end

Point(x::Int, y::Int) = Point(float(x), float(y))

Ultimately, I wish to express my profound gratitude to

for her review of this piece. This marks my debut in writing Julia blogs.

--

--

No responses yet