identify and decide on some of the insidious questions of design
- how to deal with typing - how to relate equality and mutations
This commit is contained in:
parent
f5ddfa0dbe
commit
b051845835
3 changed files with 45 additions and 2 deletions
|
|
@ -52,6 +52,33 @@
|
|||
** of elements treated as children within the scope of the
|
||||
** given record.
|
||||
**
|
||||
** \par Requirements
|
||||
**
|
||||
** GenNode elements are to be used in the diff detection and implementation.
|
||||
** This implies some requirements for the (opaque) elements used in diff:
|
||||
** - they need to support the notion of equality
|
||||
** - we need to derive a key type for usage in index tables
|
||||
** - this implies the necessity to support std::less comparisons for trees
|
||||
** - and the necessity to support hash code generation for unordered maps
|
||||
** - moreover, the elements need to be values, to be copied and handled at will
|
||||
** - it will be beneficial, if these values explicitly support move semantics
|
||||
** - in addition, the tree diffing suggests a mechanism to re-gain the fully
|
||||
** typed context, based on some kind of embedded type tag
|
||||
** - finally, the handling of changes prompts us to support installation
|
||||
** of a specifically typed <i>change handling closure</i>.
|
||||
**
|
||||
** \par monadic nature
|
||||
**
|
||||
** As suggested by the usage for representation of tree shaped data, we acknowledge
|
||||
** that GenNode is a <b>Monad</b>. We support the basic operations \em construction
|
||||
** and \em flatMap. To fit in with this generic processing pattern, the one element
|
||||
** flavours of GenNode are considered the special case, while the collective flavours
|
||||
** form the base case -- every GenNode can be iterated. The \em construction requirement
|
||||
** suggests that GenNode may be created readily, just by wrapping any given and suitable
|
||||
** element, thereby picking up the element's type. For sake of code organisation and
|
||||
** dependency management, we solve this requirement with the help of a trait type,
|
||||
** expecting the actual usage to supply the necessary specialisations on site.
|
||||
**
|
||||
** @see diff-index-table-test.cpp
|
||||
** @see diff-list-generation-test.cpp
|
||||
** @see DiffDetector
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ namespace diff{
|
|||
{ }
|
||||
|
||||
|
||||
/* === Iteration control API for IterStateWrapper== */
|
||||
/* === Iteration control API for IterStateWrapper === */
|
||||
|
||||
friend bool
|
||||
checkPoint (DiffFrame const& frame)
|
||||
|
|
|
|||
|
|
@ -7810,7 +7810,7 @@ Used this way, diff representation helps to separate structure and raw data in e
|
|||
:Chunks of raw data are attached inline to the structural diff, assuming that each element implicitly knows the kind of data to expect
|
||||
</pre>
|
||||
</div>
|
||||
<div title="TreeDiffImplementation" creator="Ichthyostega" modifier="Ichthyostega" created="201412210015" modified="201501231419" tags="Model GuiPattern design draft" changecount="17">
|
||||
<div title="TreeDiffImplementation" creator="Ichthyostega" modifier="Ichthyostega" created="201412210015" modified="201503211821" tags="Model GuiPattern design draft" changecount="18">
|
||||
<pre>//This page details decisions taken for implementation of Lumiera's diff handling framework//
|
||||
This topic is rather abstract, since diff handling is multi purpose within Lumiera: Diff representation is seen as a meta language and abstraction mechanism; it enables tight collaboration without the need to tie and tangle the involved implementation data structures. Used this way, diff representation reduces coupling and helps to cut down overall complexity -- so to justify the considerable amount of complexity seen within the diff framework implementation.
|
||||
|
||||
|
|
@ -7851,6 +7851,22 @@ Our tree diff handling framework is conceived as a direct specialisation of and
|
|||
* when a node is //opened for diffing,// we first spell out any structural alterations (added, deleted or re-ordered children)
|
||||
* the recursive descent into children happens postfix depth-first, each enclosed into a bracketing construct.
|
||||
Consequently, when an element appears in the diff description sequence, first of all, its type is immediately clear, so the receiver can use an appropriate handler for this kind of element. Moreover, if the element is of atomic type (an attribute), its value is part of the element itself and thus is known just by spelling out the element. Any structural changes can be dealt with on a completely generic level, without further knowledge about the object's nature. And finally, any internal alterations of the object and its children happen after the generic part and clearly delineated in the sequence of diff tokens -- a sub handler can be invoked recursively
|
||||
|
||||
!!!problem of the typed context
|
||||
This is a problem every introspective framework has to face. When individual elements in the data structure require specific type information for proper handling, this information must be readily available, once we start doing anything of significance with the generic data representation. A naive implementation would attach the type identification to a descriptor record exposed alongside with the data representation, thereby forcing the client back into the worst conceivable programming style, suffocating any beneficial effect the use of types and contracts might have on understandability and maintainability of the code: Either, you'll just assume everything goes well, or you end up being prepared for anything conceivable, or -- even worse -- a combination of both evils.
|
||||
There are two known remedies for this dilemma
|
||||
* a visitor (double dispatch scheme), which has its own maintenance problems
|
||||
* a //pull// approach, where the consuming client implicitly knows the full context, since it works from within this context
|
||||
Both approaches incur some complexity on behalf of the involved parties, and a runtime indirection overhead, which might be considered the residual cost for every //reflective data handling.//
|
||||
This design prefers the //pull// approach, with a special twist: we provide a completely generic, fixed implementation of the pull handling, yet offer the client to install a closure to receive any actual changes. Obviously, such a closure embodies the full typed context, and the validity of this typing can be checked at least dynamically, on //installation of the closure.// This is not as much type safety as is achievable through a visitor, but also less involved and ceremonial -- at least we can ensure the validity of the connection the moment we actually hook it up and remove the risk of running into fundamental mismatch problems in the middle of processing. The latter is the recurring plague haunting most "dynamic" systems.
|
||||
|
||||
!!!representation of objects
|
||||
It should be noted, that the purpose of this whole architecture is to deal with »remote« stuff -- things we somehow need to refer and deal with, but nothing we can influence immediately, right here: every actual manipulation has to be turned into a message and sent //elsewhere.// This is the only context, where a, maybe even partial, generic and introspective object representation makes sense.
|
||||
|
||||
Within this framework, we represent //object-like// entities through a special flavour of the GenNode: Basically, an object is a flat collection of children, yet given in accordance to a distinct protocol. The relevant ''meta'' information is spelled out first, followed by the ''attributes'' and finally the ''children''. The distinction between these lies in the mode of handling. Meta information is something we need to know before we're able to deal with the actual stuff. Prominent example is the type of the object. Attributes are considered unordered, and will typically be addressed by-name. Children are an ordered collection of recursive instances of the same data structure. (Incidentally, we do not rule out the possibility that also an attribute holds a recursive subtree; only the mode of access is what makes the distinction).
|
||||
|
||||
!!!handling of actual mutation
|
||||
This question is closely linked to the semantics of equality. In a simple list diff, this matter doesn't pose any problems; when an element is different, it is a different element, and this change can be encoded as a deletion and insertion of a new element. Not so in tree diff handling. We do not want to delete and re-build whole subtrees, because some tiny bit is altered somewhere down. Thus, a recursive sub-structure can be considered //the same entity,// yet still //mutated.// Our diff handling framework deals with the identity first, followed by an recourse into investigating //inner changes.// This recursive investigation is spelled out as a bracketed construct, which can be processed by recursive invocation. In the end, at the level of the tree leaves, handling those inner mutations boils down to invoking the //mutation closure,// as mentioned above. The knowledge of type context is thus confined to the receiving client, as long as every GenNode implementation offers support to detect an inner mutation and allows to install and invoke such a specifically typed closure to deal with the mutation. The twist to note is the point, //where// this closure is installed: it certainly doesn't make sense to install it on the generating side.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="TreeDiffModel" creator="Ichthyostega" modifier="Ichthyostega" created="201410270313" modified="201503172340" tags="Model GuiPattern spec draft" changecount="52">
|
||||
|
|
|
|||
Loading…
Reference in a new issue