How to initialize/construct deep nested structs in Julia? - struct

I want to create some deeper nested structs. It seems the Setfield.jl package is sufficient to fill them. But I'm struggling with the initialization of the (empty) structs to fill them later iteratively. These are the structs looks like:
struct Bands
B0::String
B1::String
B2::String
B3::String
B4::String
B5::String
.
.
.
B25::String
end
struct Masks
mask1::Bands
mask2::Bands
mask3::Bands
mask4::Bands
mask5::Bands
mask6::String
end
I've tried inner constructors and as described here https://docs.julialang.org/en/v1/manual/constructors/#Incomplete-Initialization-1. It seems that I have to set all values before like described at https://jw3126.github.io/Setfield.jl/stable/intro/.
How can I initialize the nested struct, in that case 131 values at once?

You can create default constructors that just put empty objects for all fields which you can update later, for example
julia> struct Bands
B0::String
B1::String
# Create a constructor that puts an empty String ("") for each field
Bands() = new(("" for _ in 1:length(fieldnames(Bands)))...)
# Define a vararg constructor
Bands(args...) = new(args...)
end
julia> struct Masks
mask1::Bands
mask2::Bands
# Create a constructor that puts an empty Bands (Bands()) for each field
Masks() = new((Bands() for _ in 1:length(fieldnames(Masks)))...)
# Define a vararg constructor
Masks(args...) = new(args...)
end
julia> masks = Masks()
Masks(Bands("", ""), Bands("", ""))
julia> using Setfield
julia> masks = #set masks.mask1 = Bands("hello", "world") # Set a field of masks
Masks(Bands("hello", "world"), Bands("", ""))
julia> masks = #set masks.mask2.B0 = "hello" # Set a field of masks.mask2
Masks(Bands("hello", "world"), Bands("hello", ""))

For your scenario using Parameters.jl will result with a much cleaner code (additionally since String is immutable your Bands struct needs to be mutable.
using Parameters
#with_kw mutable struct Bands
B0::String = ""
B1::String = ""
end
#with_kw struct Masks
mask1::Bands = Bands()
mask2::Bands = Bands()
end
In this way your code is clean, readable and you do not need to define a huge load of constructors.

Related

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

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

Why are NamedTuples and (immutable) structs separate?

Can someone explain why NamedTuples and immutable structs are separate instead of NamedTuples being an anonymous struct like there are anonymous functions function (x) x^2 end? They look like they have the same structure conceptually (I would also like to know if they have a different memory layout), though they have different methods to access their fields (example below). It seems very plausible to implement the NamedTuple methods for structs, but I may just not be aware of a good reason not to do that.
struct X; a; b; c; end
Xnt = NamedTuple{(:a,:b,:c), Tuple{Any, Any, Any}}
t1 = (10, 20.2, 30im)
#
#
# t1[1] indexing by position
# t1[1:2] slicing
# for el in t1 iteration
x1 = X(t1...)
# x1.a getting field
xnt1 = Xnt(t1)
# xnt1.a getting field
# xnt1[:a] indexing by field
# xnt1[1] indexing by position
#
# for el in t1 iteration
Every single NamedTuple instance with the same names and field types is of the same type. Different structs (types) can have the same number and type of fields but are different types.
A named tuple has names for each column in the tuple. Named tuples are an alternative to a dataframe. When instantiating the namedTuple, you pass the list of field names as a list. Named tuples create a specification contract for expected fields and reduce the chance of code breaking.

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)

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[])

Resources