Efficient "parent" reference for structs

I have got the following two structs

abstract type _MyType end

mutable struct Type1 <: _MyType 
    a::Int
end

mutable struct Type2 <: _MyType
    a::Int
end

Furthermore there is some sort of “parent” / “global” context, in the form of

mutable struct Model
    b::Int
end

Now, I do have the following methods of function foo:

foo(model::Model, x::Type1) = model.b + x.a
foo(model::Model, x::Type2) = model.b - x.a

Now, while this works, it became a hassle to maintain, since there will always be only one model at a given time, and every “Type” is always added and registered in the model. Therefore I would like to change my structs and keep a reference to this “parent” to make internals cleaner.

That would involve changing (e.g.) to (the _model would be set on construction and would never change!):

mutable struct Type1 <: _MyType 
    _model::Model
    a::Int
end

foo(x::Type1) = x._model.b + x.a

I am wondering what would be the best performing approach to do that; regarding both computing time, as well as memory requirement (since I’m looking at a rather large number of these structs). Is that way sufficient or should that be a Ref, or …?

Do your structs have to be mutable?

Unfortunately yes. They are actually defined like this (stripped the kwdef for a MWE):

Base.@kwdef mutable struct Unit <: _CoreComponent
    ...
end

Background: Basically they are constructed based on a user defined config file, and then multiple functions “build” up each struct. That “building up” part needs to be after initially constructing all of them, so no possibility to construct them “already built” (to get rid of the mutable).
Furthermore, of course I could split it up into a “config”-struct (this could now be non-mutable and hold a reference to model) and a “final”-struct, but then this would again need to be mutable and hold a reference to its “config”-twin.

What about using closures?

mutable struct Type1 <: _MyType 
    a::Int
end

mutable struct Model
    b::Int
end

foo(model::Model, x::Type1) = model.b + x.a

make_foo(model::Model) = x -> foo(model, x)

When you make an instance of Model, you pass it to make_foo which will return foo with that instance fixed.

1 Like

That’s a nice idea, but won’t solve the initial problem of “making internals cleaner”.

It’s hard to justify why I’m looking for that without sharing a large code base, but it boils down to the problem of many functions modifying the given struct. So basically I have many complex calculations, that each look like foo(model, component), with component being of TypeX (with multiple types). Every TypeX has its own version of foo(...) (dispatched based on the component). Of course I could use a closure, which would be easy for a few functions that I need to wrap, but if each foo(...) possibly calls multiple bar(model, component) internally, that gets out of hand real quick.

Essentially, allowing bar(component) using component._model.myattr would be cleaner than always passing down the model through all “layers”. This question really just comes from the “problem”, that I am looking to improve how it “looks” and less how it “functions”. Basically model is a global variable, there is a single “instance of truth” at all times, and it is used in every single file and (almost) all functions of the code base. Passing it down through all layers is just there to prevent it from actually being a global variable.

What about

function (model::Model)(x::Type1)
    return model.b + x.a
end

function (model::Model)(x::Type2)
    return model.b - x.a
end

x1 = Type1(1)
x2 = Type2(2)
model = Model(3)

model(x1)   # = 4
model(x2)   # = 1

I don’t understand the problem here. You need only one make_foo for all methods of foo because foo dispatches.

The easiest to look at it is: I have multiple “nested” functions, for example:

function dostuff()
    model = Model()
    
    ...

    for comp in all_components
        ... = _foo(model, component)
    end

    ...

    return model
end

_foo(model, component) = 2 * _bar(model, component)
_bar(model, component) = -_calc(model, component)
_calc(model, component) = model.b + component.a

I’m looking to simplify the “internal” part by not having to pass the model all the time, changing the last three lines to:

_foo(component) = 2 * _bar(component)
_bar(component) = -_calc(component)
_calc(component) = component._model.b + component.a

I’m not sure how I should achieve that using closures without complicating things further (that would involve externally doing something like _make_calc(model) = x -> _template_calc(model, x); _calc = _make_calc(model)) which does not seem to make the internal process “cleaner”.


But, I am still unsure what the downfall of the initially mentioned approach (maybe as Ref) is?