Recursive, mutual, and multi-phase initialization
In this section we shall consider more complex initialization processes that require the coordinated initialization of possibly large object collections.
A) Recursive initialization
Assume you want to construct a tree structure of objects that are all of the same class, and every object x may depend on some or all objects contained in the sub-trees beginning at x. The objects in the sub-trees may in turn contain backward/reverse pointers to x or depend on members of x that have already been initialized. No matter whether you prefer a depth-first or a breadth-first recursive initialization process, on the basis of the following mechanisms all this should be no problem in Lava: Within an initializer of a new tree node
- you may create links to sub-objects by assigning the results of
new operations to member variables of the currently initialized object.
- you may pass the current self object as input parameters to the initializers of sub-objects. If self has not yet been fully initialized at that point, then the formal input parameter must be declared "closed" and you are not allowed to access members of the parameter within the invoked initializer of the sub-object.
- you may pass the values of (closed/non-closed) local variables to initializers of sub-objects (through closed/non-closed, respectively, formal input parameters). A member self.x of the current self object cannot be passed as a parameter as long as self has not yet been completely initialized, even if self.x is already complete. But, instead of self.x, you may pass a local variable having the same value as self.x as a parameter, of course.
B) Mutual initialization
The optional initialize clause of the Lava
declare construct facilitates the collective initialization of mutually dependent objects (i.e., that may point to one another). Although you can also use recursive initializer calls to this end, the initializer clause of
declare is much clearer in many cases of a more complex dependency between objects:
In the recursive case you would have to pass objects through several steps of initializer calls to the objects that depend on the former objects, and you would have to provide corresponding parameters for the intermediate initializers of this call tree, whereas with the
initialize clause all involved objects are available at once for being passed as parameters to initializer calls:
The initialize clause provides a more symmetric, one-level initialization style, in contrast to the unsymmetric, hierarchical initialization by recursion.
On the other hand, recursive initialization may be more appropriate or the only feasible approach in cases where you have to initialize a dynamic collection of objects whose size isn't known at programming time.
See the cyclicDep.lava and cyclicDepError.lava samples.
C) Multi-phase initialization
While the single-phase recursive initialization of a tree-structure of objects is a rather straightforward process in Lava, the construction of complex object collections in several completely separate phases may require a more thorough planning and separation of the object structures related to the individual phases in order to comply with the complete initialization checks of Lava.
Let's again consider the tree structure of sub-section A) above, but let's assume that it shall be initialized in two phases. Then you have several options how to separate the data associated with either phase. For instance
- you can provide two member variables on the top-level of every tree node, one pointing to the phase-1 data, the other pointing to the phase-2 data. The value of the latter would be optional and thus would not have to be initialized during phase 1 but, if at all, only during phase 2. The entire top-level object would have to be a state object in order to permit the phase-2 member to be set subsequently after the completion of phase 1.
- you can declare separate classes for phase-1 nodes and phase-2 nodes, where the phase-2 class would be derived from the phase-1 class and contain the additional phase-2 data. This solution wouldn't depend on the optional character of the phase-2 member of the preceding solution and thus guarantee a more safe initialization of the phase-2 part of a tree node, while solution 1 could erroneously fail to initialize the phase-2 member during phase 2.
The disadvantage of solution 2 is that it requires a reconstruction of the entire tree structure in phase 2, particularly of the pointers between tree nodes that now would have to point to phase-2 nodes instead of phase-1 nodes. To avoid the latter disadvantage you could alternatively provide reverse pointers from the phase-1 nodes to the corresponding phase-2 nodes and reuse the sub-tree pointers from phase 1 and use those additional reverse pointers to find the corresponding phase-2 targets of the phase-1 sub-tree pointers.
See also
Complete initialization checks
Unfinished/closed/opaque/quarantined objects