I want to declare two structures, one of which contains many of the same elements of the other. In a simple example, it would be, for example:
struct A
a :: Int64
end
struct B
a :: Int64
b :: Float64
end
What I am aiming is to be able to write something like (the following example does not work, but probably makes the point):
DataA = "a :: Int64"
struct A
eval(DataA)
end
struct B
eval(DataA)
b :: Int64
end
Of course, this would be used in a situation where the DataA is a long list of properties.
The same sort of construct could be used to define two structures with the same data structure, one mutable and the other not, for instance:
DataA = "a :: Int64"
struct A
eval(DataA)
end
mutable struct MutA
eval(DataA)
end
Actually I would like to do that to organize some data reading code, by reading the data first to a mutable structure and then passing it to an unmutable one.
I searched the docs for metaprograming options, but I could not find out how to make that work.
What about initializing the immutable structure with the data from the mutable one?
For instance, something like:
DataA = quote
x
y
end
@eval struct A
$DataA
end
@eval struct MutA
$DataA
end
MutA() = MutA(0,0) # wandering if there is a better way to initialize these values as well
z = MutA()
z.a = 1
z.b = 2
w = A(expand(z)) # something like this
Where the expand function returns the list of values of z. For instance, this function does that, but it returns a string and thus the definition of w fails:
function expand(a)
nfields = length(fieldnames(typeof(a)))
ifield = 0
list = ""
for field in fieldnames(typeof(a))
ifield = ifield + 1
if ifield < nfields
list = "$list$(@eval a.$field),"
else
list = "$list$(@eval a.$field)"
end
end
return list
end
I understand that I could build explicitly a constructor, such as
A( x :: MutA ) = A( x.a, x.b )
but again, for structures with lots of fields that might become cumbersome.
As a general note, avoid using strings for metaprogramming. It really isn’t the right tool in a language that has first class support for constructing expressions.
If you just want to shove the fields from one type into another (and performance is not crucial) in the same order you can do something like
The idea of having a mutable structure with the same data structure as the immutable one is that I need to read a lot of different parameters from a file, and then create an immutable structure with all that data. Thus, since the immutable structure has to be generated at once,
x = A(1,2,"abc", ... )
I need to create temporary variables for all the fields anyway, read all these temporary variables and, at the end, create the immutable x.
What I was thinking was to use a mutable structure with the same structure of the immutable one to read clearly and beautifully all the data, using something as:
y = MutA()
y.a = data[1]
y.b = parse(Float64,somestring)
to finally initalize the immutable x variable, which will be used in performance critical code, using, as you suggest,
x = A(getfield.((y,),1:nfields(y))...)
Perhaps I am missing something trivial about immutable constructors which would turn out the task much easier?
Just to finish: I actually like the result obtained using the two structures, one mutable and the other not. Of course, it is only about how easy is to understand the code afterwards. But one example of the kind of thing I am aiming to use is:
AtomData = quote
index :: Int64
residue :: String
name :: String
x :: Float64
y :: Float64
z :: Float64
b :: Float64
occup :: Float64
end
@eval mutable struct ReadAtom
$AtomData
end
ReadAtom() = ReadAtom(0,"X","X",0.,0.,0.,0.,0.)
@eval struct Atom
$AtomData
end
Atom( atom :: ReadAtom ) = Atom([ getfield(atom,i) for i in 1:nfields(atom) ]...)
The only think I would like to improve here is the constructor for the mutable structure without parameters, which I still have to modify if I add an additional parameter to the AtomData list. Ideally I would like to provide default parameters already there.
The use of Parameters to initialize the values is what I want indeed.
However, using simply Atom() would not allow me to actually fill this variable
with the actual data (edit: using the names of the fields), which will be read afterwards from input files, since the Atom
structure is immutable.
But I completely understand that I could replace the mutable structure with a simple
list, or with a Dict. The choice relies only on how the code looks like. I like the idea of using
x = ReadAtom() # x is muttable and will be used for data reading
x.index = 1
x.name = "Carbon"
y = Atom(x) # y is immutable and will be used in critical code
Thank you very much for all your help. I learnt a lot of things.
EDIT:
An alternative way to initialize with default parameters a muttable struct, which is convenient in my case, is this:
function initdata(X :: DataType)
list = []
for type in X.types
if type == Int64
push!(list,0)
end
if type == String
push!(list,"X")
end
if type == Float64
push!(list,0.)
end
end
y = X(list...)
return y
end
with which the actual code would look like:
x = initdata(ReadAtom) # muttable with default parameters
x.index = 1
x.name = "Carbon"
y = Atom(x) # immutable