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:
Fischlurch 2016-02-19 21:33:22 +01:00
parent afbba968b5
commit dd1afef970
3 changed files with 94 additions and 154 deletions

View file

@ -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>

View file

@ -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.
}
};

View file

@ -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&#xfc;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&#xfc;ssen die intern repr&#xe4;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>