Ticket #956: decide layout and handling of GenNode elements

to carry out that rather obvious step, I was bound to consider
all the implications of choosing a given layout and handling pattern
for our external structure representation.

Finally, I settled upon the following decisions
- the value space represented within the DataCap is flat, not further structured
- the distinction between "attribute" and "nested object" is merely conceptual
  and will be enforced solely by the diff detection / representation protocol
- basically, a nested subtree may appear as an attribute; the difference
  between attributes and children lies solely in the way of access and referral:
  by-name vs. positional
- it is pointless to save space for the representation of the discriminator ID
- but we can omit any further explicit type tag, because
- we do *not* support programming by switch-on-type, and thus
- we do *not* support full introspection, only a passive type-safety check
- this is *not* a limitation, since we acknowledge that GenNode is a *Monad*
- and the partial function needed within any flatMap implementation
  maps naturally onto our Variant-Visitor; thus
- the DataCap can basically just *be* a Variant
- and GenNode has just to supply the neccessary shaffolding
  to turn that into a full fledged Monad implementation, including
  direct construction by wrapping a value and flatMap with tree walk
This commit is contained in:
Fischlurch 2015-05-02 01:11:39 +02:00
parent 5d056f032d
commit 6de24bc7f0
7 changed files with 76 additions and 42 deletions

View file

@ -23,14 +23,14 @@
/** @file gen-node.hpp
** Generic building block for tree shaped (meta)data structures.
** Representation built from GenNode elements is intended to support
** A representation built from GenNode elements is intended to support
** introspection of data structures and exchange of mutations in the
** form of \link diff-language.hpp diff messages. \endlink
**
** Despite of the name, GenNode is \em not meant to be an universal
** data representation; rather it is limited to embody a fixed hard
** wired set of data elements relevant to stand-in for attributes
** and scope contents of the lumiera high-level data model.
** wired set of data elements, able to stand-in for attributes
** and sub scope contents of the lumiera high-level data model.
**
** \par Anatomy of a GenNode
**
@ -58,10 +58,10 @@
** 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
** - this implies the necessity to support std::less comparisons for tree-maps
** - and the necessity to support hash code generation for unordered (hash)maps
** - moreover, the elements need to be values, able to be copied and handled at will
** - it will be beneficial for these values to support move semantics explicitly
** - 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
@ -90,11 +90,21 @@
#define LIB_DIFF_GEN_NODE_H
#include "lib/hash-indexed.hpp" ///< @warning needs to be first, see explanation in lib/hash-standard.hpp
#include "lib/error.hpp"
#include "lib/variant.hpp"
#include "lib/time/timevalue.hpp"
#include "lib/diff/record.hpp"
//#include "lib/util.hpp"
//#include "lib/format-string.hpp"
#include "lib/format-util.hpp"
#include "lib/variant.hpp"
#include "lib/util.hpp"
//#include <vector>
#include <string>
//#include <map>
@ -103,15 +113,40 @@ namespace diff{
namespace error = lumiera::error;
//using util::_Fmt;
using std::string;
class DataCap;
class GenNode;
using DataValues = meta::Types<int
,int64_t
,short
,char
,bool
,double
,string
,time::Time
,time::Duration
,time::TimeSpan
,hash::LuidH
,Record<GenNode>
>;
class DataCap
: public Variant<DataValues>
{
public:
template<typename X>
DataCap(X&& x)
: Variant<DataValues>(std::forward<X>(x))
{ }
};
/** generic data element node within a tree */
class GenNode
{
public:
};

View file

@ -53,6 +53,7 @@
#include "lib/error.hpp"
#include "lib/symbol.hpp"
#include "lib/diff/gen-node.hpp"
//#include "lib/util.hpp"
//#include "lib/format-string.hpp"
@ -78,7 +79,7 @@ namespace diff{
////////TODO only preliminary....
typedef Literal ID;
typedef struct{ } Attribute;
using Attribute = DataCap;
}
@ -124,7 +125,7 @@ namespace diff{
}
virtual void
setAttribute (ID id, Attribute newValue)
setAttribute (ID id, Attribute& newValue)
{
std::cout << "Empty Base Impl: apply a value change to the named attribute"<<std::endl; ////////////////TODO empty implementation should be NOP
}
@ -146,13 +147,12 @@ namespace diff{
function<void(string)> change_;
virtual void
setAttribute (ID id, Attribute newValue)
setAttribute (ID id, Attribute& newValue)
{
// Decorator-style chained invocation of inherited implementation
PAR::setAttribute(id, newValue);
string dummy("blubb");
change_(dummy);
change_(newValue.get<string>());
}
ChangeOperation(function<void(string)> clo, PAR const& chain)

View file

@ -137,7 +137,7 @@ namespace meta{
using EmptyBase = struct{};
struct EmptyBase { };
template<class IFA, class BASE = EmptyBase>
class VirtualCopySupportInterface

View file

@ -43,7 +43,7 @@
** lib::PolymorphicValue to hold types implementing a common interface.
**
** \par implementation notes
** We use a similar "double capsule" implementation technique as for lib::OpaqueHolder.
** We use a "double capsule" implementation technique similar to lib::OpaqueHolder.
** In fact, Variant is almost identical to the latter, just omitting unnecessary flexibility.
** The outer capsule exposes the public handling interface, while the inner, private capsule
** is a polymorphic value holder. Since C++ as such does not support polymorphic values,
@ -136,11 +136,7 @@ namespace lib {
= meta::InstantiateForEach<typename TYPES::List, ValueAcceptInterface>;
/////TODO: *nur noch*
/////TODO: - den gefährlichen Cast aus AccessCasted weg
/////TODO: - *nur* der Unit-Test für OpaqueBuffer ist betroffen. Diesen durch direkten statischen Cast ersetzen
/////TODO: - unit-Test für AccessCasted nachliefern
/////TODO: - forward-call für Konstruktor im Buffer? aber nur, wenn es sich verifizieren läßt!
/////TODO: - is it possible directly to forward the constructor invocation to the object within the buffer? Beware of unverifiable generic solutions!
}//(End) implementation helpers
@ -409,14 +405,8 @@ namespace lib {
} // namespace lib
/* == diagnostic helper == */
#ifdef LIB_FORMAT_UTIL_H
namespace lib {
template<typename TYPES>
Variant<TYPES>::operator string() const
@ -429,11 +419,14 @@ namespace lib {
Variant<TYPES>::Buff<TY>::operator string() const
{
return "Variant|"
#ifdef LIB_FORMAT_UTIL_H
+ util::str (this->access(),
(util::tyStr<TY>()+"|").c_str()
);
}
}// namespace lib
(util::tyStr<TY>()+"|").c_str())
#endif
;
}
}// namespace lib
#endif /*LIB_VARIANT_H*/

View file

@ -23,6 +23,7 @@
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/diff/gen-node.hpp"
#include "lib/diff/record.hpp"
//#include <utility>
@ -55,8 +56,8 @@ namespace test{
/*****************************************************************************//**
* @test Verify properties of a special collection type meant for external representation
* of object-loke data.
* @test Verify properties of a special collection type shaped for
* external representation of object-like data.
*
* @see IndexTable
* @see DiffListApplication_test

View file

@ -106,7 +106,8 @@ namespace test{
cout << "concrete TreeMutator type="<< demangleCxx (showType (mutator)) <<endl;
CHECK (isnil (localData));
mutator.setAttribute("zoing", {});
Attribute testAttribute(string ("boing"));
mutator.setAttribute ("zoing", testAttribute);
CHECK (!isnil (localData));
cout << "localData changed to:"<<localData<<endl;
}

View file

@ -2212,12 +2212,16 @@ For this Lumiera design, we could consider making GOP just another raw media dat
&amp;rarr;see in [[Wikipedia|http://en.wikipedia.org/wiki/Group_of_pictures]]
</pre>
</div>
<div title="GenNode" creator="Ichthyostega" modifier="Ichthyostega" created="201501171413" modified="201501171431" tags="Model GuiIntegration GuiPattern def draft" changecount="8">
<div title="GenNode" creator="Ichthyostega" modifier="Ichthyostega" created="201501171413" modified="201505011656" tags="Model GuiIntegration GuiPattern def draft" changecount="10">
<pre>//Abstract generic node element to build a ~DOM-like rendering of Lumiera's [[session model|HighLevelModel]].//
GenNode elements are values, yet behave polymorphic. They are rather light-weight, have an well established identity and can be compared. They are //generic// insofar they encompass several heterogeneous ordering systems, which in themselves can not be subsumed into a single ordering hierarchy. The //purpose// of these generic nodes is to build a symbolic representation, somewhere &quot;outside&quot;, at a level where the fine points of ordering system relations do not really matter.
To be more specific, within the actual model there are [[Placements|Placement]]. These refer to [[M-Objects|MObject]]. Which in turn rely on [[Assets|Asset]]. Moreover, we have some processing rules, and -- last but not least -- the &quot;objects&quot; encountered in the model have state, visible as attributes of atomic value type (integral, floating point, string, boolean, time, time ranges and [[quantised time entities|TimeQuant]]).
A generic node may //represent any of these kind// -- and it may have ~GenNode children, forming a tree.</pre>
A generic node may //represent any of these kind// -- and it may have ~GenNode children, forming a tree. Effectively all of this together makes ~GenNode a ''Monad''.
!to reflect or not reflect
When dealing with this external model representation, indeed there are some rather global concerns which lend themselves to a generic programming style. Simply because, otherwise, we'd end up explicating and thereby duplicating the structure of the model all over the code. Frequently, such a situation is quoted as the reason to demand introspection facilities on any data structure. We doubt this is a valid conclusion. Since introspection allows to accept just //any element// -- followed by an open-ended //reaction on the received type// -- we might arrive at the impression that our code reaches a maximum of flexibility and &quot;openness&quot;. Unfortunately, this turns out to be a self-deception, since code to do any meaningful operation needs pre-established knowledge about the meaning of the data to be processed. More so, when, as in any hierarchical data organisation, the relevant meaning is attached to the structure itself, so consequently this pre-established knowledge tends to be scattered over several, superficially &quot;open&quot; handler functions. What looks open and flexible at first sight is in fact littered with obscure and scattered, non obvious additional presumptions.
This observation from coding practice gets us to the conclusion, that we do not really want to support the full notion of data and type introspection. We //do want// some kind of passive matching on structure, where the receiver explicitly has to supply structural presuppositions. In a fully functional language with a correspondingly rich type system, a partial function (pattern match) would be the solution of choice. Under the given circumstances, we're able to emulate this pattern based on our variant visitor -- which basically calls a different virtual function for each of the types possibly to be encountered &quot;within&quot; a ~GenNode.</pre>
</div>
<div title="GlobalPipe" modifier="Ichthyostega" created="201007110200" modified="201202032308" tags="Model spec draft">
<pre>Each [[Timeline]] has an associated set of global [[pipes|Pipe]] (global busses), similar to the subgroups of a sound mixing desk.
@ -7813,7 +7817,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="201503292044" tags="Model GuiPattern design draft" changecount="30">
<div title="TreeDiffImplementation" creator="Ichthyostega" modifier="Ichthyostega" created="201412210015" modified="201504290114" tags="Model GuiPattern design draft" changecount="31">
<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.
@ -7864,7 +7868,7 @@ Both approaches incur some complexity on behalf of the involved parties, and a r
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 &quot;dynamic&quot; 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.
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 some, 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).
@ -7935,7 +7939,7 @@ On receiving the terms of this &quot;diff language&quot;, it is possible to gene
i.e. a ''unified diff'' or the ''predicate notation'' used above to describe the list diffing algorithm, just by accumulating changes.
</pre>
</div>
<div title="TreeMutator" creator="Ichthyostega" modifier="Ichthyostega" created="201503292115" modified="201504131311" tags="Model Concepts GuiPattern design draft" changecount="39">
<div title="TreeMutator" creator="Ichthyostega" modifier="Ichthyostega" created="201503292115" modified="201504290109" tags="Model Concepts GuiPattern design draft" changecount="41">
<pre>The TreeMutator is an intermediary to translate a generic structure pattern into heterogeneous local invocation sequences.
!Motivation
@ -8016,7 +8020,7 @@ The contract is as follows:
!!!Architecture
The distinguising trait or Lumiera's diff handling framework is to treat a //diff language// as a scope, where some verbs or predications gain a specific meaning. While a concrete {{{DiffApplicator}}} binds to a specific agent or target environment, the meaning of the binding remains implicit. This approach bears on the observation, that dealing with structural differences actually addresses //several layers of language.// An odered collection is one of them, some kind of object notion another one, and the exact meaning of structure yet another one.
In accordance with this understanding, the TreeMutator is shaped to form the attachment point of such a language construct onto a given structural context, which, through this very construct, remains opaque and decoupled. In the simple exemplary case of list diffing, the {{{DiffApplicator}}} requries the specialisation of some {{{DiffApplicationStrategy}}} to the concrete data container in use, most likely a STL {{{vector}}}. In a similar vein, a {{{DiffApplicator}}} for structural transformations requires the {{{DiffApplicationStrategy}}}'s specialisation to a TreeMutator. Which -- consequently -- needs to expose an API well suited to implement what a structural diff language might want to express. Yet, in addition, the TreeMutator also needs a binding API to be linked into the actual structures subject to manipulation.
In accordance with this understanding, the TreeMutator is shaped to form the attachment point of such a language construct onto a given structural context, which, through this very construct, remains opaque and thus decoupled. In the simple exemplary case of list diffing, the {{{DiffApplicator}}} requries the specialisation of some {{{DiffApplicationStrategy}}} to the concrete data container in use, most likely a STL {{{vector}}}. In a similar vein, a {{{DiffApplicator}}} for structural transformations requires the {{{DiffApplicationStrategy}}}'s specialisation to a TreeMutator. Which -- consequently -- needs to expose an API well suited to implement what a structural diff language might want to express. Yet, in addition, the TreeMutator also needs a binding API, allowing him to be linked into the actual structures subject to manipulation.
At this point, practical performance considerations settle the remaining decisions: Language application is a ''double dispatch'' at least, so we won't be able to get below two indirections ever. We could strive at getting close to this theoretical minimum though. Unfortunately, one of the indirections is taken by the verbs of the language, while the other one is consumed by decoupling the language from the Applicator or Interpreter, i.e. abstracting the diff representation from the meaning of the described changes when considered within a specific target context -- which is the very core of using a diff representation.
So we get to choose between