How to actually replace OO structures in Julia?

Hi,

I need your help with the following common task which, when described using classes looks like below.

We start with a reference or idealized model.
Later, the model is going to be refined, properties and methods get modified, replaced, or added.

How to best implement something like this in Julia without OO objects, inheritance, and overloading?

class ref
	prop1
	prop2
	meth1 = f1ref // of many
	meth2 = f2ref
end

class mod1 // (of some)
	prop1
	prop2
	propnew
	meth1 = f1ref
	meth2 = f2mod
	meth3 = f3new
end

Naively, I’d write something like this:

struct ref
    prop1
    prop2
end

meth1(obj::ref) = f1ref()
meth2(obj::ref) = f2ref()

struct mod1
    prop1
    prop2
    propnew
end

meth1(obj::mod1) = f1ref()
meth2(obj::mod1) = f2mod()
meth3(obj::mod1) = f3new()

Note that I have ignored the convention to uppercase type names here and left the fields of the struct untyped (which will be bad for performance).

If you have lots of shared fields prop1 … propN, you might want to group them in a separate struct, say,

struct SharedProperties
    prop1
    prop2
    ....
end

If you really want to mimick some form of structural inheritance, there a packages that give you something like it, e.g. Classes.jl or StructuralInheritance.jl. But, really, you should think about whether you actually need to inherit (parts of) the data structure. In Julia, we generally design type hierarchies by inheriting behavior rather than structure.

Let me also point you to an old Julia resource by Chris on this topic that I liked reading back in 2017: Type-Dispatch Design: Post Object-Oriented Programming for Julia - Stochastic Lifestyle

4 Likes

Thx @carstenbauer

In my case, the naive approach would require a lot of manual code repetition.

Can dispatch be used to define “default” methods?
Would reduce typing as well.

I’m afraid I will need some sort of OO mimicking.
ref, mod1 and the others cannot be simply abstracted away but have their own “personality”.
In real life they represent different hardware implementations to be judged for their performance.

The objects can be described by a common configuration file, does this help?

Julia (through multiple dispatch) always chooses the most specific method of a generic function. So you can readily define a default for, say, meth1 like so

meth1(obj) = default() # not type annotation in the function signature

Yes, looks indeed helpful, was not aware of these, thx!
I was also looking further along

The objects can be described by a common configuration file, does this help?

and found Model configuration/parameterization file - General Usage - JuliaLang.
The suggested flow with Parameters.jl could do without objects.

Here’s a simple, idiomatic example of how dispatch is used to define default methods (adapted from this talk):

abstract type Animal end
speak(x::Animal) = "Some noise"

struct Dog <: Animal end
speak(x::Dog) = "Woof"
struct Cat <: Animal end
speak(x::Cat) = "Meow"
struct Snake <: Animal end

speak(Dog()) # "Woof"
Speak(Cat()) # "Meow"
speak(Snake()) # "Some noise"

Dog and Cat types have a specific speak method which gets called, but Snake doesn’t, so it falls back to the more generic method for abstract Animals.

5 Likes