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