Can I access a struct by name, eg A = field1, get struct.A? - struct

Here's a psuedocode implementation of what I would be looking for within Julia:
struct Example
field1::Float64
field2::Float64
end # End struct
example = Example(1., 2.)
function modifystruct(mystruct, fieldname)
mystruct.fieldname +=10
return mystruct
end
modifystruct(example, field1)
# In this instance I would want to have example.field1 = 11.
How would I actually do this? I want to provide the fieldname as something like a string, and have my struct."whateverfieldname" get modified as such. I should add that I do NOT want to code something in like this:
function modifystruct(mystruct, fieldname)
if fieldname = "fieldname1"
mystruct.field1 +=10
end
if fieldname = "fieldname2"
mystruct.field2 +=10
end
return mystruct
end
Largely due to how versatile I want this code to be. I may be using different types of structs for my program, so the closest I can get to directly accessing by the name of the field, the better. Is there any method or implementation that can do this for me?

Sure, that's setproperty!(value, name, x) and getproperty(value, name):
function modifystruct(mystruct, fieldname)
new_field = getproperty(mystruct, fieldname) + 10
setproperty!(mystruct, fieldname, new_field)
return mystruct
end
As DecowVR rightly notes, this requires mystruct to be mutable.
If you want to do this repeatedly and with nested properties, you might be interested in lenses such as those provided by Setfield.jl.

Firstly, whould be noticed that in order to be able to modify an struct, it needs to be mutable:
julia> mutable struct Example
field1::Float64
field2::Float64
end
julia> example = Example(1., 2.)
Example(1.0, 2.0)
And now, a simple aproach would be to use Julia Symbols. A symbol is nothing else but an expression like :var. Can be used as shown:
julia> example.:field1
1.0
However, if we create a variable that stores the symbol, it won't work:
julia> v = :field1
:field1
julia> example.v
ERROR: type Example has no field v
Stacktrace:
[1] getproperty(x::Example, f::Symbol)
# Base ./Base.jl:42
[2] top-level scope
# REPL[18]:1
This is due to the order in which the Julia Interpreter works. If we want to evaluate firstly the variable, and then the expression, it is as easy as:
julia> #eval example.$v
1.0
So the complete function would be as follows:
julia> function modify_struct(mystruct::Example, fieldname::Symbol)
#eval $mystruct.$fieldname += 10
end

Related

Julia: How to generate new immutable struct by modifying original immutable struct at user-supplied field?

Suppose I have some immutable struct, e.g.
struct Person
name::Symbol
age::Int
end;
I want to write a function
function copyWithModification(original_person::Person, fieldToChange::String, valueForNewField)::Person
that returns a new Person struct just like the old one except that the value of the field specified in fieldToChange has been set to valueForNewField. How do I do this?
My current attempt uses Setfield and metaprogramming
using Setfield
function copyWithModification(original_person::Person, fieldToChange::String, valueForNewField)::Person
return eval(Meta.parse("#set original_person." * fieldToChange * " = " * string(valueForNewField)))
end
This does not work because the eval is performed in global scope and thus does not have access to the original_person object:
julia> struct Person
name::Symbol
age::Int
end;
julia> using Setfield
julia> function copyWithModification(original_person::Person, fieldToChange::String, valueForNewField)::Person
return eval(Meta.parse("#set original_person." * fieldToChange * " = " * string(valueForNewField)))
end
copyWithModification (generic function with 1 method)
julia> person_local_scope = Person(:test, 10)
Person(:test, 10)
julia> copyWithModification(person_local_scope, "age", 20)
ERROR: UndefVarError: original_person not defined
Stacktrace:
[1] top-level scope at /Users/lionstarr/.julia/packages/Setfield/XM37G/src/sugar.jl:182
[2] eval at ./boot.jl:330 [inlined]
[3] eval(::Expr) at ./client.jl:425
[4] copyWithModification(::Person, ::String, ::Int64) at ./REPL[3]:2
[5] top-level scope at REPL[5]:1
julia>
I should note I am not concerned with the performance of this code; it will only be called once or twice. The point is to save code replication and human error as the struct I actually want to use this code on is a lot larger.
If you are not concerned with performance, using plain introspection is fine and very simple in your case:
function copy_with_modification1(original::T, field_to_change, new_value) where {T}
val(field) = field==field_to_change ? new_value : getfield(original, field)
T(val.(fieldnames(T))...)
end
For example, it yields the following results:
julia> struct Person
name::Symbol
age::Int
end
julia> p = Person(:Joe, 42)
Person(:Joe, 42)
julia> using BenchmarkTools
julia> #btime copy_with_modification1($p, :age, 43)
666.924 ns (7 allocations: 272 bytes)
Person(:Joe, 43)
In order to regain efficiency, the same kind of technique can be implemented in such a way that listing fields happens at compile-time. Here is an example using a generated function:
# Can't have closures inside generated functions, so the helper function
# is declared outside
function val_(original, field, field_to_change, new_value)
field == field_to_change ? new_value : getfield(original, field)
end
#generated function copy_with_modification2(original, field_to_change, new_value)
# This is the "compile-time" part
T = original # here `original` refers to the type of the argument
fields = fieldnames(T) # fieldnames is called compile-time
# This is the "run-time" part
quote
# We broadcast only over `fields`, other arguments are treated as scalars
$T(val_.(Ref(original), $fields, Ref(field_to_change), Ref(new_value))...)
end
end
The performance is now much better:
julia> #btime copy_with_modification2($p, :age, 43)
2.533 ns (0 allocations: 0 bytes)
Person(:Joe, 43)
Such function is already defined in Setfield and there is no need to reinvent the wheel!
julia> using Setfield
julia> p = Person(:Smith, 10)
Person(:Smith, 10)
julia> setproperties(p, age=20)
Person(:Smith, 20)
More than one field can be set at a time, see ?setproperties for more details.
You don't need to use metaprogramming for this. I think this "normal" function manages to do what you need.
function Person(p :: Person,fieldtochange,newvalue)
newparams = [] # This array will store a new list of parameters
# This loop will iterate in all the fields (obtained via [fieldnames][1])
# of the struct Person and compare with the given field,
# if it coincides, adds the new value to the newparams array,
# if not, get the values of the original person using
# getproperty and add them to the array.
for currentfield in fieldnames(Person)
if currentfield == fieldtochange
push!(newparams,newvalue)
else
push!(newparams,getproperty(p,currentfield)) #[2]
end
end
return Person(newparams...) #Construct a new person with the new parameters
# using '...' for [splatting][3].
end
In this case I named the function "Person" to make it another constructor but you can change the name to the one you wanted.
1 https://docs.julialang.org/en/v1/base/base/#Base.fieldnames
[2] https://docs.julialang.org/en/v1/base/base/#Base.getproperty
[3] https://docs.julialang.org/en/v1/base/base/#...

Variable fieldnames in Julia mutable structs

I would like to able to get fields from a mutable struct in Julia using a variable.
e.g.
mutable struct myType
my_field1::Int = 1
my_field2::Int = 2
my_field3::Int = 3
end
And then let's imagine you declare a particular instance of this struct using struct_instance = myType()
How can you extract the value of a field from an instance of this mutable struct in a variable fashion?
Let's say you want to assign the value of my_struct.field[X] to a variable, using a for-loop, so that the particular field you're currently accessing depends on the variable X:
foo = zeros(Int64, 3)
for X = 1:3
foo(X) = struct_instance.field[X]
end
I don't know how to actually implement the above for-loop -- what I wrote above is just pseudo-code above. In MATLAB you would use the following notation, for instance:
foo = zeros(1,3)
for x = 1:3
foo(x) = struct_instance.(sprintf('field%d',x))
end
Thanks in advance.
For the code at the beginning of your example to work you need the Paramaters package, otherwise you are not able to have default values (the code from your example throws an error). I use it very often exactly in situations where I need a struct to use to represent a bunch of variables.
using Parameters
#with_kw mutable struct MyType
my_field1::Int = 1
my_field2::Int = 2
my_field3::Int = 3
end
This generates also a set of keyword methods that can be used to set the fields programmatically when creating the object. Have a look at the following code:
julia> vals = [Symbol("my_field$i") =>10i for i in 2:3 ]
2-element Array{Pair{Symbol,Int64},1}:
:my_field2 => 20
:my_field3 => 30
julia> MyType(;vals...)
MyType
my_field1: Int64 1
my_field2: Int64 20
my_field3: Int64 30
We have created here a set of field names and now we are using it when creating an object. This approach is particularly useful when you could consider using immutable object instead of mutable ones (immutable objects are always much faster).
You can mutate the object using setfield!:
julia> for i in 1:2
setfield!(m,Symbol("my_field$i"), 20i)
end
julia> m
MyType
my_field1: Int64 20
my_field2: Int64 40
my_field3: Int64 30
I think that when you are coming from Matlab this would be the most convenient way to layout your structs.
The function to get fields from a struct is fieldnames:
julia> mutable struct A
x
y
z
end
julia> fieldnames(A)
(:x, :y, :z)
To set those field values* programmatically, you can use setproperty!:
julia> a = A(1,1,1)
A(1,1,1)
julia> a.x
1
julia> setproperty!(a, :x, 2)
2
julia> a.x
2
With a for loop,
julia> for i in fieldnames(A)
setproperty!(a, i, 3)
end
julia> a
A(3,3,3)

How do I print a struct instance in the console without displaying it's whole stacktrace in Julia?

There are two Julia files :
file1.jl
struct MyStruct
attribute :: String
end
file2.jl
module myModule
include("file1.jl")
println(MyStruct("a"))
end
When I execute file2.jl, it's supposed to print an instance of MyStruct
user#user-MS-7823:~$ julia cpsld/file2.jl
Main.myModule.MyStruct("a")
user#user-MS-7823:~$
I was expecting it to only print the struct name (so MyStruct("a")), however I got surprised as it also prints all the modules name, which is a bit annoying...
Is there any way not to print the modules along with the struct name?
You can use IOContext like this:
module myModule
struct MyStruct
attribute :: String
end
println(IOContext(stdout, :module=>myModule), MyStruct("a"))
end
(the fact that you have definitions split into two files is irrelevant here)
Thanks #Bogumił Kamiński for your solution.
The problem is that this only works for printing (ok, I know that's why I asked for lol).
Imagine I have three files.
file1.jl
module myFirstModule
struct MyStruct
attribute :: String
end
end
file2.jl
module mySecondModule
include("file1.jl")
include("file3.jl")
myThirdModule.hello(myFirstModule.MyStruct("a"))
end
file3.jl
module myThirdModule
include("file1.jl")
function hello(arg)
println("argument : ", arg)
println("isa test : ", isa(arg, myFirstModule.MyStruct))
end
end
Well, there, the second module creates an instance of myStruct, and calls the "hello" function from the third module with that instance as an argument.
That is the console output :
argument : Main.mySecondModule.myFirstModule.MyStruct("a")
isa test : false
As you can see, the result of the isa test is false.
This acutally means that arg's type isn't myFirstModule.MyStruct, but when I print arg, as you can see I'm getting this : Main.mySecondModule.myFirstModule.MyStruct("a")
Does it mean that myFirstModule.MyStruct != Main.mySecondModule.myFirstModule.MyStruct ?

Problem in pushing a value to an element of struct in Julia

Say I have a struct:
mutable struct DataHolder
data1::Vector{Float64}
data2::Vector{Float64}
function DataHolder()
emp = Float64[]
new(emp, emp)
end
end
d = DataHolder()
When I try to push a value to only one element of struct d by doing:
push!(d.data1, 1.0)
the value is pushed not only d.data1 but also d.data2. Indeed, the REPL says
julia> d
DataHolder([1.0], [1.0])
How can I push a value to only one element of the struct??
Your problem is not in push!, but rather in the inner constructor of DataHolder. Specifically:
emp = Float64[]
new(emp, emp)
This code pattern means that the fields of the new DataHolder both point toward the same array (in memory). So if you mutate one of them (say, via push!), you also mutate the other.
You could instead replace those two lines with:
new(Float64[], Float64[])
to get the behaviour you want.
More generally, although it is not forbidden, your use of the inner constructor is a bit odd. Typically, inner constructors should have a method signature corresponding exactly to the fields of your struct, and the inner constructor itself is typically only used to provide a set of universal tests that any new DataHolder should undergo.
Personally I would re-write your code as follows:
mutable struct DataHolder
data1::Vector{Float64}
data2::Vector{Float64}
function DataHolder(data1::Vector{Float64}, data2::Vector{Float64})
#Any tests on data1 and data2 go here
new(data1, data2)
end
end
DataHolder() = DataHolder(Float64[], Float64[])
If you don't need to do any universal tests on DataHolder, then delete the inner constructor entirely.
Final food for thought: Does DataHolder really need to be mutable? If you only want to be able to mutate the arrays in data1 and data2, then DataHolder itself does not need to be mutable, because those arrays are already mutable. You only need DataHolder to be mutable if you plan to completely re-allocate the values in those fields, e.g. an operation of the form dh.data1 = [2.0].
UPDATE AFTER COMMENT:
Personally I don't see anything wrong with DataHolder() = DataHolder(Float64[], ..., Float64[]). It's just one line of code and you never have to think about it again. Alternatively, you could do:
DataHolder() = DataHolder([ Float64[] for n = 1:10 ]...)
which just splats an a vector of empty vectors into the constructor arguments.
#ColinTBowers has answered your question. Here is a very simple and a more general implementation:
struct DataHolder # add mutable if you really need it
data1::Vector{Float64}
data2::Vector{Float64}
end
DataHolder() = DataHolder([], [])
Perhaps you'd like to allow other types than Float64 (because why wouldn't you?!):
struct DataHolder{T}
data1::Vector{T}
data2::Vector{T}
end
DataHolder{T}() where {T} = DataHolder{T}([], [])
DataHolder() = DataHolder{Float64}() # now `Float64` is the default type.
Now you can do this:
julia> DataHolder{Rational}()
DataHold{Rational}(Rational[], Rational[])
julia> DataHolder()
DataHold{Float64}(Float64[], Float64[])

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?
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
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.
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

Resources