A question on the type system

Hello!

Say I have two nested(?) structures as

mutable struct MyInnerStruct
    index :: Int64
    vector :: Vector{Float64}
    boolean_flag :: Bool
    a :: Float64
    b :: Float64
    c :: Float64
end

struct MyOuterStruct
    name :: String
    vector :: Vector{MyInnerStruct}
end

and at run time I have

mystruct = MyOuterStruct("my_out_struct", [MyInnerStruct(i, rand(100), rand(Bool), rand(1), rand(1), rand(1)) for i=1:100])

where the value of MyInnerStruct.boolean_flag is not known in advance. Then I want to do some calculations on all the elements of MyOuterStruct.vector

function dosomething!(s::MyInnerStruct)
    if s.boolean_flag
        s.vector .+= 2.0*s.index
    else
        s.vector .-= 1.0
    end
end

dosomething!.(mystruct.vector)

depending on the boolean flag I’m switching implementation.

I thought I can exploit the type system to simplify this by defining differently my structs

mutable struct MyInnerStructTrue
    index :: Int64
    vector :: Vector{Float64}
    a :: Float64
    b :: Float64
    c :: Float64
end

mutable struct MyInnerStructFalse
    vector :: Vector{Float64}
    a :: Float64
    b :: Float64
    c :: Float64
end

struct MyOuterStructUnion
    name :: String
    vector :: Vector{Union{MyInnerStructTrue, MyInnerStructFalse}}
end

dosomething!(s::MyInnerStructTrue) = s.vector .+= 2.0*s.index
dosomething!(s::MyInnerStructFalse) = s.vector .-= 1.0

and use it as

innerstructs = []
for i=1:100
    b = rand(Bool)
    if b
        push!(innerstructs, MyInnerStructTrue(i, rand(100), rand(1), rand(1), rand(1)))
    else
        push!(innerstructs, MyInnerStructFalse(rand(100), rand(1), rand(1), rand(1)))
    end
end
mystruct = MyOuterStructUnion("my_out_struct", innerstructs)
dosomething!.(mystruct.vector)

this works but now I have a new struct (MyInnerStructFalse) which is identical to the previous (MyInnerStructMyInnerStructTrue) one except for a single entry (index).

Is there a way of merging types? Something along the lines of

mutable struct MyInnerStructTrue
    index :: Int64
    <- put here MyInnerStructFalse
end

mutable struct MyInnerStructFalse
    vector :: Vector{Float64}
    a :: Float64
    b :: Float64
    c :: Float64
end

Not really, no.

In your setting, I would recommend not using the type system for that; just say that index == -1 corresponds to the false case and be done with it.

The type system thing is amazing if you need extendibility across modules / packages / authors, or if you need compiler specialization (e.g. StaticArrays). If you don’t need that, it’s mostly better to keep stuff simple.

You could try

struct MyInnerInner
    vector :: Vector{Float64}
    a :: Float64
    b :: Float64
    c :: Float64
end
mutable struct MyInnerFalse
inner::MyInnerInner
end
mutable struct MyInnerTrue
index::Int64
inner::MyInnerInner
end

but I don’t recommend it.

It does give identical data layout to what you want, though:

julia> struct MyInnerInner
           vector :: Vector{Float64}
           a :: Float64
           b :: Float64
           c :: Float64
       end

julia> mutable struct MyInnerFalse
       inner::MyInnerInner
       end

julia> mutable struct MyInnerTrue
       index::Int64
       inner::MyInnerInner
       end

julia> sizeof(MyInnerInner)
32

julia> sizeof(MyInnerFalse)
32

julia> sizeof(MyInnerTrue)
40
4 Likes

thanks @foobar_lv2
I was wondering whether this wasn’t necessary at all