Create a function as a attribute to a struct

I want to create a clock object with the attributes clock.initial, clock.current and clock.elapsed.

I now want functions like initialize, update, play and pause. Is there a way to have something like

clock.initialize()

setting clock.initial = time()

clock.update()

setting clock.current = time() and setting clock.elapsed = clock.current - clock.initial

Or something similar?

julia> Base.@kwdef mutable struct Mytype
       mystring::String = ""
       f::Function = function sayhello(string=mystring) println("hello " * string) end
       end
Mytype

julia> m = Mytype(mystring = "Aryan")
Mytype("Aryan", var"#sayhello#27"{String}("Aryan"))

julia> m.f()
hello Aryan

I was able to do this… add a function to some struct but when trying something like:

set_init::Function = function something!()
    global initial = time()
end

I get an error message saying

setfield! fields of Types should not be changed.

How do I achieve what I want?
(I know I can have initialize(clock::Clock) and have initialize(clock) be called as a separate function which wouldn’t hinder with other predefined functions either or something similar; I’m just exploring the language and the metaprogramming sides of it while working on a cool project)

It’s possible, but then you are fighting the language, and its fundamental paradigm. It will probably also hurt performance. If you strongly prefer this style, you may not be happy with Julia.

In idiomatic Julia, you define methods externally to the structs:

initialize(clock) 
update(clock) 

etc.

2 Likes

The pattern you’re describing is Object Oriented Programming – data and behavior stored together in classes/objects.

Base Julia does not support OOP directly. Instead, it separates data and behavior into structs and functions. So an OOP approach might be

class Clock():
    init_time
    elapsed_time
    def update(self):
        self.elapsed_time = current_time - self.init_time


clock = Clock()
clock.update()

But the Julian way is

mutable struct Clock
    init_time
    elapsed_time
end

function update(c::Clock)
    c.elapsed_time = current_time - c.init_time
end

clock = Clock()
update(clock)

There are packages that will let you emulate OOP in Julia, but the are not necessary because OOP vs multiple dispatch (Julia’s pattern) are both fully sufficient design patterns.

Edit: both of the code snippets above are pseudocode and will not run!

2 Likes

Concur with the other responses here. Multiple dispatch can be awesome if you give it a chance, but trying to shoe-horn an OOP structure into it just makes the language feel clunky, versus playing to its strengths.

As a rule of thumb, if your function is defined the same for all structs then the function should be defined separately, like the one in @mrufsvold’s example. If you have a case where the function is unique to the specific instance of a struct, then it might make sense to store it in the struct. Essentially, structs are just containers for storing a specific set of information (state), while functions can be used to change that information or act on it.

@DNF @mike.ingold @mrufsvold thank you all for replying.
I understand that Julia uses multiple dispatch and how nice it is! I just want to play around with the language, understand the extents of metaprogramming and all that.

I was just asking for a way to do it. I know it’s not efficient and all that but CAN we do it in Julia? If yes, how?

1 Like
julia> mutable struct Clock
           time::UInt64
           initialize::Function
           update::Function
           play::Function
           pause::Function
           function Clock(time=0x0)
               self::Clock = new(
                   time,
                   ()->println("initialize ", self.time += 1),
                   ()->println("update ", self.time += 1),
                   ()->println("play ", self.time += 1),
                   ()->println("pause ", self.time += 1),
                )
            end
       end

julia> Base.show(io::IO, c::Clock) = print(io, "time = $(c.time)")


julia> c = Clock()
time = 0

julia> c.play()
play 1

julia> c.play()
play 2

julia> c.pause()
pause 3

julia> c.update()
update 4

julia> c.initialize()
initialize 5
1 Like