WIP: consider what kind of changes to support and how
especially the nagging question is: - do we need to support children of mixed type - and how can we support those, wihtout massively indirected calls
This commit is contained in:
parent
afbba968b5
commit
dd1afef970
3 changed files with 94 additions and 154 deletions
|
|
@ -33,65 +33,25 @@
|
|||
** private data structure.
|
||||
**
|
||||
** ## Design considerations
|
||||
** While -- conceptually -- our tree diff handling can be seen as an extension
|
||||
** and generalisation of list diffing, the decision was \em not to embody this
|
||||
** extension into the implementation technically, for sake of clarity. More so,
|
||||
** since the Record, which serves as foundation for our »External Tree Description«,
|
||||
** was made to look and behave like a list-like entity, but built with two distinct
|
||||
** scopes at implementation level: the attribute scope and the contents scope. This
|
||||
** carries over to the fine points of the list diff language semantics, especially
|
||||
** when it comes to fault tolerance and strictness vs fuzziness in diff application.
|
||||
** The implementation is thus faced with having to deal with an internal focus and
|
||||
** a switch from scope to scope, which adds a lot of complexity. So the list diff
|
||||
** application strategy can be seen as blueprint and demonstration of principles.
|
||||
** So this use case is implemented on the same conceptual framework used for
|
||||
** the generic tree diff application, which in turn is -- conceptually -- an
|
||||
** extension of applying a list diff. But, again, we follow the route _not_ to
|
||||
** explicate those conceptual relations in the form of inheritance. This would
|
||||
** be implementation re-use, as opposed to building a new viable abstraction.
|
||||
** No one outside the implementation realm would benefit from such an abstraction,
|
||||
** so we prefer to understand the tree diff language as the abstraction, which
|
||||
** needs to embodied into two distinct contexts of implementation.
|
||||
**
|
||||
** Another point in question is whether to treat the diff application as
|
||||
** manipulating a target data structure, or rather building a reshaped copy.
|
||||
** The fact that GenNode and Record are designed as immutable values seems to favour
|
||||
** the latter, yet the very reason to engage into building this diff framework was
|
||||
** how to handle partial updates within a expectedly very large UI model, reflecting
|
||||
** the actual session model in Proc-Layer. So we end up working on a Mutator,
|
||||
** which clearly signals we're going to reshape and re-rig the target data.
|
||||
**
|
||||
** \par related
|
||||
** Closely related to this generic application of tree changes is the situation,
|
||||
** where we want to apply structural changes to some non-generic and private data
|
||||
** structure. In fact, it is possible to _use the same tree diff language_ for
|
||||
** this specific case, with the help of an _adapter_. Thus, within our diff
|
||||
** framework, we provide a _similar binding_ for the DiffApplicator, but
|
||||
** then targetted towards such an [structure adapter](\ref TreeMutator)
|
||||
**
|
||||
** ## State and nested scopes
|
||||
** Within the level of a single #Record, our tree diff language works similar to
|
||||
** the list diff (with the addition of the \c after(ID) verb, which is just a
|
||||
** shortcut to accept parts of the contents unaltered). But after possibly rearranging
|
||||
** the contents of an "object" (Record), the diff might open some of its child "objects"
|
||||
** by entering a nested scope. This is done with the \c mut(ID)....emu(ID) bracketing
|
||||
** construct. On the implementation side, this means we need to use a stack somehow.
|
||||
** The decision was to manage this stack explicitly, as a std::stack (heap memory).
|
||||
** Each entry on this stack is a "context frame" for list diff. Which makes the
|
||||
** tree diff applicator a highly statefull component.
|
||||
**
|
||||
** Even more so, since -- for \em performance reasons -- we try to alter the
|
||||
** tree shaped data structure \em in-place. We want to avoid the copy of possibly
|
||||
** deep sub-trees, when in the end we might be just rearranging their sequence order.
|
||||
** This design decision comes at a price tag though
|
||||
** - it subverts the immutable nature of \c Record<GenNode> and leads to
|
||||
** high dependency on data layout and implementation details of the latter.
|
||||
** This is at least prominently marked by working on a diff::Record::Mutator,
|
||||
** so the client has first to "open up" the otherwise immutable tree
|
||||
** - the actual list diff on each level works by first \em moving the entire
|
||||
** Record contents away into a temporary buffer and then \em moving them
|
||||
** back into new shape one by one. In case of a diff conflict (i.e. a
|
||||
** mismatch between the actual data structure and the assumptions made
|
||||
** for the diff message on the sender / generator side), an exception
|
||||
** is thrown, leaving the client with a possibly corrupted tree, where
|
||||
** parts might even still be stashed away in the temporary buffer,
|
||||
** and thus be lost.
|
||||
** We consider this unfortunate, yet justified by the very nature of applying a diff.
|
||||
** When the user needs safety or transactional behaviour, a deep copy should be made
|
||||
** before attaching the #DiffApplicator
|
||||
** ### Yet another indirection
|
||||
** Unfortunately this leads to yet another indirection layer: Implementing a
|
||||
** language in itself is necessarily a double dispatch (we have to abstract the
|
||||
** verbs and we have to abstract the implementation side). And now we're decoupling
|
||||
** the implementation side from a concrete data structure. Which means, that the
|
||||
** use will have to provide a set of closures (which might even partially generated
|
||||
** functors) to translate the _implementation actions_ underlying the language into
|
||||
** _concrete actions_ on local data.
|
||||
**
|
||||
** @see DiffVirtualisedApplication_test
|
||||
** @see DiffTreeApplication_test
|
||||
** @see DiffListApplication_test
|
||||
** @see GenNodeBasic_test
|
||||
|
|
@ -124,15 +84,15 @@ namespace diff{
|
|||
|
||||
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992
|
||||
/**
|
||||
* Interpreter for the tree-diff-language to work on GenNode elements
|
||||
* A concrete strategy to apply a structural diff to a target data structure
|
||||
* made from #Record<GenNode> elements. This data structure is assumed to be
|
||||
* recursive, tree-like. But because Record elements are conceived as immutable
|
||||
* and value-like, the tree diff application actually works on a Rec::Mutator
|
||||
* wrapping the target record to be altered through consuming the diff.
|
||||
* @throws lumiera::error::State when diff application fails due to the
|
||||
* target sequence being different than assumed by the given diff.
|
||||
* @see #TreeDiffInterpreter explanation of the verbs
|
||||
* Interpreter for the tree-diff-language to work on arbitrary, undiclosed
|
||||
* local data structures. The key point to note is that this local data is
|
||||
* not required to implement any specific interface. The only requirement is
|
||||
* the ability somehow to support the basic operations of applying a structural
|
||||
* diff. This is ensured with the help of a _customisable adapter_ the TreeMutator.
|
||||
* @throws lumiera::error::State when diff application fails structurally.
|
||||
* @throws _unspecified errors_ when delegated operations fail.
|
||||
* @see TreeDiffInterpreter explanation of the verbs
|
||||
* @see DiffVirtualisedApplication_test demonstration of usage
|
||||
*/
|
||||
template<>
|
||||
class DiffApplicationStrategy<Rec::Mutator>
|
||||
|
|
|
|||
|
|
@ -72,27 +72,17 @@ namespace test{
|
|||
|
||||
|
||||
/***********************************************************************//**
|
||||
* @test Demonstration/Concept: a description language for tree differences.
|
||||
* The representation is given as a linearised sequence of verb tokens.
|
||||
* In addition to the verbs used for list diffing, here we additionally
|
||||
* have to deal with nested scopes, which can be entered thorough a
|
||||
* bracketing construct \c mut(ID)...emu(ID).
|
||||
* This test demonstrates the application of such diff sequences
|
||||
* - in the first step, an empty root #Record<GenNode> is populated
|
||||
* with a type-ID, three named attribute values, three child values
|
||||
* and a nested child-Record.
|
||||
* - the second step demonstrates various diff language constructs
|
||||
* to alter, reshape and mutate this data structure
|
||||
* After applying those two diff sequences, we verify the data
|
||||
* is indeed in the expected shape.
|
||||
* @remarks to follow this test, you should be familiar both with our
|
||||
* \link diff::Record generic data record \endlink, as well as with
|
||||
* the \link diff::GenNode variant data node \endlink. The key point
|
||||
* to note is the usage of Record elements as payload within GenNode,
|
||||
* which allows to represent tree shaped object like data structures.
|
||||
* @see GenericRecordRepresentation_test
|
||||
* @see GenNodeBasic_test
|
||||
* @see DiffListApplication_test
|
||||
* @test Demonstration: apply a stuctural change to unspecified private
|
||||
* data structures, with the help of an [dynamic adapter](\ref TreeMutator)
|
||||
* - we use private data classes, defined here in the test fixture
|
||||
* to represent "just some" pre-existing data structure.
|
||||
* - we re-assign some attribute values
|
||||
* - we add, re-order and delete child "elements", without knowing
|
||||
* what these elements actually are and how they are to be handled.
|
||||
* - we recurse into mutating such an _"unspecified"_ child element.
|
||||
* @see DiffTreeApplication_test generic variant of tree diff application
|
||||
* @see GenericTreeMutator_test base operations of the adapter
|
||||
* @see tree-diff-mutator-binding.hpp
|
||||
* @see diff-tree-application.hpp
|
||||
* @see tree-diff.hpp
|
||||
*/
|
||||
|
|
@ -102,21 +92,28 @@ namespace test{
|
|||
{
|
||||
using DiffSeq = iter_stl::IterSnapshot<DiffStep>;
|
||||
|
||||
DiffSeq
|
||||
attributeDiff()
|
||||
{
|
||||
// prepare for direkt attribute assignement
|
||||
GenNode attrib1_mut(ATTRIB1.idi.getSym(), 11);
|
||||
|
||||
return snapshot({ins(TYPE_X)
|
||||
, pick(ATTRIB1)
|
||||
, del(ATTRIB2)
|
||||
, ins(ATTRIB3)
|
||||
, set(attrib1_mut)
|
||||
});
|
||||
}
|
||||
|
||||
DiffSeq
|
||||
populationDiff()
|
||||
{
|
||||
return snapshot({ins(TYPE_X)
|
||||
, ins(ATTRIB1)
|
||||
, ins(ATTRIB2)
|
||||
, ins(ATTRIB3)
|
||||
return snapshot({ins(ATTRIB2)
|
||||
, ins(CHILD_A)
|
||||
, ins(CHILD_A)
|
||||
, ins(CHILD_T)
|
||||
, ins(CHILD_T)
|
||||
, ins(SUB_NODE)
|
||||
, mut(SUB_NODE)
|
||||
, ins(CHILD_B)
|
||||
, ins(CHILD_A)
|
||||
, emu(SUB_NODE)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -125,34 +122,21 @@ namespace test{
|
|||
mutationDiff()
|
||||
{
|
||||
// prepare for direkt assignement of new value
|
||||
// NOTE: the target ID will be reconstructed, including hash
|
||||
GenNode childA_upper(CHILD_A.idi.getSym(), "A");
|
||||
GenNode childT_later(CHILD_T.idi.getSym()
|
||||
,Time(CHILD_T.data.get<Time>() + Time(0,1)));
|
||||
|
||||
return snapshot({after(Ref::ATTRIBS) // fast forward to the first child
|
||||
, pick(CHILD_A) // rearrange childern of mixed types...
|
||||
, find(CHILD_T)
|
||||
, pick(CHILD_A)
|
||||
, set(childT_later) // immediately assign to the child just picked
|
||||
, find(CHILD_T) // fetch the other Time child
|
||||
, del(CHILD_A) // delete a child of type Y
|
||||
, skip(CHILD_T)
|
||||
, del(CHILD_T)
|
||||
, pick(Ref::CHILD) // pick a child anonymously
|
||||
, mut(Ref::THIS) // mutate the current element (the one just picked)
|
||||
, skip(CHILD_T)
|
||||
, mut(CHILD_A) // mutate a child of type Y referred by ID
|
||||
, ins(ATTRIB3)
|
||||
, ins(ATTRIB_NODE) // attributes can also be nested objects
|
||||
, find(CHILD_A)
|
||||
, del(CHILD_B)
|
||||
, ins(CHILD_NODE)
|
||||
, ins(CHILD_T)
|
||||
, skip(CHILD_A)
|
||||
, mut(CHILD_NODE)
|
||||
, ins(TYPE_Y)
|
||||
, ins(ATTRIB2)
|
||||
, emu(CHILD_NODE)
|
||||
, set(childA_upper) // direct assignment, target found by ID (out of order)
|
||||
, mut(ATTRIB_NODE) // mutation can be out-of order, target found by ID
|
||||
, ins(CHILD_A)
|
||||
, ins(CHILD_A)
|
||||
, ins(CHILD_A)
|
||||
, emu(ATTRIB_NODE)
|
||||
, emu(Ref::THIS)
|
||||
, emu(CHILD_A)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -164,45 +148,14 @@ namespace test{
|
|||
Rec& subject = target;
|
||||
DiffApplicator<Rec::Mutator> application(target);
|
||||
|
||||
// Part I : apply diff to populate
|
||||
// Part I : apply attribute changes
|
||||
application.consume(populationDiff());
|
||||
|
||||
CHECK (!isnil (subject)); // nonempty -- content has been added
|
||||
CHECK ("X" == subject.getType()); // type was set to "X"
|
||||
CHECK (1 == subject.get("α").data.get<int>()); // has gotten our int attribute "α"
|
||||
CHECK (2L == subject.get("β").data.get<int64_t>()); // ... the long attribute "β"
|
||||
CHECK (3.45 == subject.get("γ").data.get<double>()); // ... and double attribute "γ"
|
||||
auto scope = subject.scope(); // look into the scope contents...
|
||||
CHECK ( *scope == CHILD_A); // there is CHILD_A
|
||||
CHECK (*++scope == CHILD_T); // followed by a copy of CHILD_T
|
||||
CHECK (*++scope == CHILD_T); // and another copy of CHILD_T
|
||||
CHECK (*++scope == MakeRec().appendChild(CHILD_B) // and there is a nested Record
|
||||
.appendChild(CHILD_A) // with CHILD_B
|
||||
.genNode(SUB_NODE.idi.getSym())); // and CHILD_A
|
||||
CHECK (isnil(++scope)); // thats all -- no more children
|
||||
|
||||
// Part II : apply the second diff
|
||||
// Part II : apply child population
|
||||
application.consume(mutationDiff());
|
||||
|
||||
// Part II : apply child mutations
|
||||
application.consume(mutationDiff());
|
||||
CHECK (join (subject.keys()) == "α, β, γ"); // the attributes weren't altered
|
||||
scope = subject.scope(); // but the scope was reordered
|
||||
CHECK ( *scope == CHILD_T); // CHILD_T
|
||||
CHECK (*++scope == CHILD_A); // CHILD_A
|
||||
Rec nested = (++scope)->data.get<Rec>(); // and our nested Record, which too has been altered:
|
||||
CHECK (nested.get("γ").data.get<double>() == 3.45); // it carries now an attribute "δ", which is again
|
||||
CHECK (nested.get("δ") == MakeRec().appendChild(CHILD_A) // a nested Record with three children CHILD_A
|
||||
.appendChild(CHILD_A) //
|
||||
.appendChild(CHILD_A) //
|
||||
.genNode("δ")); //
|
||||
auto subScope = nested.scope(); // and within the nested sub-scope we find
|
||||
CHECK ( *subScope != CHILD_A); // CHILD_A has been altered by assigment
|
||||
CHECK (CHILD_A.idi == subScope->idi); // ...: same ID as CHILD_A
|
||||
CHECK ("A" == subScope->data.get<string>()); // ...: but mutated payload
|
||||
CHECK (*++subScope == MakeRec().type("Y") // a yet-again nested sub-Record of type "Y"
|
||||
.set("β", int64_t(2)) // with just an attribute "β" == 2L
|
||||
.genNode(CHILD_NODE.idi.getSym())); // (and an empty child scope)
|
||||
CHECK (*++subScope == CHILD_T); // followed by another copy of CHILD_T
|
||||
CHECK (isnil (++subScope)); //
|
||||
CHECK (isnil (++scope)); // and nothing beyond that.
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -695,6 +695,33 @@
|
|||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1455913713933" ID="ID_155948375" MODIFIED="1455913721066" TEXT="getypte Kinder">
|
||||
<icon BUILTIN="help"/>
|
||||
<node CREATED="1455913726963" ID="ID_514338534" MODIFIED="1455913730857" TEXT="kommt es vor">
|
||||
<icon BUILTIN="help"/>
|
||||
</node>
|
||||
<node CREATED="1455913731723" ID="ID_1269232747" MODIFIED="1455913750301" TEXT="Problem umgehen">
|
||||
<icon BUILTIN="help"/>
|
||||
</node>
|
||||
<node CREATED="1455913800081" ID="ID_552808081" MODIFIED="1455913813985" TEXT="Typ-Info transportieren">
|
||||
<icon BUILTIN="help"/>
|
||||
</node>
|
||||
<node CREATED="1455913759727" ID="ID_1318158783" MODIFIED="1455913777192" TEXT="Modell">
|
||||
<node CREATED="1455913778429" ID="ID_884750408" MODIFIED="1455913785088" TEXT="sub-Collections">
|
||||
<node CREATED="1455913847683" ID="ID_1829117976" MODIFIED="1455913860029" TEXT="es gibt effektiv mehrere Kinder-Sammlungen"/>
|
||||
<node CREATED="1455913860594" ID="ID_631826706" MODIFIED="1455913869013" TEXT="wir verwalten intern für jede eine Position"/>
|
||||
<node CREATED="1455913877183" ID="ID_1295733719" MODIFIED="1455913906711" TEXT="forwarding by type"/>
|
||||
</node>
|
||||
<node CREATED="1455913785555" ID="ID_1777441123" MODIFIED="1455913793358" TEXT="transparenter Filter">
|
||||
<node CREATED="1455913912323" ID="ID_1775410993" MODIFIED="1455913922605" TEXT="es gibt nominell nur eine Kinder-Folge"/>
|
||||
<node CREATED="1455913924857" ID="ID_505406246" MODIFIED="1455913935019" TEXT="wir müssen die intern repräsentieren"/>
|
||||
<node CREATED="1455913938287" ID="ID_749740013" MODIFIED="1455913957888" TEXT="passenden Adapter">
|
||||
<node CREATED="1455913959452" ID="ID_1132015088" MODIFIED="1455913969855" TEXT="erst beim Zugriff"/>
|
||||
<node CREATED="1455913970443" ID="ID_164083502" MODIFIED="1455913995371" TEXT="jeweils Typ feststellen"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
|
|
|
|||
Loading…
Reference in a new issue