Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

mutable fields in Julia struct

I couldn't find an answer in both stackoverflow and the Julia docs to the following "design problem":

Let's say I want to define the following object

struct Person
birthplace::String
age::Int
end

Since Person is immutable, I'm happy that nobody can change the birthplace of any Person created, nonetheless, this also implies that when time passes, I cannot change their age either...

On the other hand, if I define the type Person as

mutable struct Person
birthplace::String
age::Int
end

I can now make them age, but I don't have the safety I had before on the birthplace, anyone can access it and change it.

The workaround I found so far is the following

struct Person
birthplace::String
age::Vector{Int}
end

where obviously age is a 1-element Vector.
I find this solution quite ugly and definitely suboptimal as I have to access the age with the square brackets every time.

Is there any other, more elegant, way to have both immutable and mutable fields in an object?

Maybe the problem is that I am missing the true value of having either everything mutable or immutable within a struct. If that's the case, could you explain me that?

like image 334
stefabat Avatar asked Jan 23 '18 12:01

stefabat


3 Answers

For this particular example it seems better to store the birthdate rather than the age, since the birthdate is also immutable, and it is simple enough to calculate the age from that information, but perhaps this is just a toy example.


I find this solution quite ugly and definitely suboptimal as I have to access the age with the square brackets every time.

Usually you would define a getter, i.e. something like age(p::Person) = p.age[1] that you use instead of accessing the field directly. With this you avoid the "ugliness" with the brackets.

In this case, where we only want to store a single value, it is also possible to use a Ref (or possibly a 0-dimensional Array), something like:

struct Person
    birthplace::String
    age::Base.RefValue{Int}
end
Person(b::String, age::Int) = Person(b, Ref(age))
age(p::Person) = p.age[]

with usage:

julia> p = Person("earth", 20)
Person("earth", 20)

julia> age(p)
20
like image 135
fredrikekre Avatar answered Nov 16 '22 21:11

fredrikekre


You've received some interesting answers, and for the "toy example" case, I like the solution of storing the birth-date. But for more general cases, I can think of another approach that might be useful. Define Age as its own mutable struct, and Person as an immutable struct. That is:

julia> mutable struct Age ; age::Int ; end

julia> struct Person ; birthplace::String ; age::Age ; end

julia> x = Person("Sydney", Age(10))
Person("Sydney", Age(10))

julia> x.age.age = 11
11

julia> x
Person("Sydney", Age(11))

julia> x.birthplace = "Melbourne"
ERROR: type Person is immutable

julia> x.age = Age(12)
ERROR: type Person is immutable

Note that I can't alter either field of Person, but I can alter the age by directly accessing the age field in the mutable struct Age. You could define an accessor function for this, ie:

set_age!(x::Person, newage::Int) = (x.age.age = newage)

julia> set_age!(x, 12)
12

julia> x
Person("Sydney", Age(12))

There is nothing wrong with the Vector solution discussed in another answer. It is essentially accomplishing the same thing, since array elements are mutable. But I think the above solution is neater.

like image 9
Colin T Bowers Avatar answered Nov 16 '22 19:11

Colin T Bowers


In Julia 1.8, you can use

mutable struct Person
       age::Int
       const birthplace::String
end

Cf. https://docs.julialang.org/en/v1.8-dev/manual/types/#Composite-Types

like image 3
Marco Meer Avatar answered Nov 16 '22 20:11

Marco Meer